Repository: zeromicro/go-zero
Pull request #1747 (2022-04-02 14:10:50)

feat(goctl): supports api  multi-level importing
Resolves: #1744


Patch:
diff --git a/core/collection/cache.go b/core/collection/cache.go
index b7c887fb..35411055 100644
--- a/core/collection/cache.go
+++ b/core/collection/cache.go
@@ -98,13 +98,18 @@ func (c *Cache) Get(key string) (interface{}, bool) {
 
 // Set sets value into c with key.
 func (c *Cache) Set(key string, value interface{}) {
+	c.SetWithExpire(key, value, c.expire)
+}
+
+// SetWithExpire sets value into c with key and expire with the given value.
+func (c *Cache) SetWithExpire(key string, value interface{}, expire time.Duration) {
 	c.lock.Lock()
 	_, ok := c.data[key]
 	c.data[key] = value
 	c.lruCache.add(key)
 	c.lock.Unlock()
 
-	expiry := c.unstableExpiry.AroundDuration(c.expire)
+	expiry := c.unstableExpiry.AroundDuration(expire)
 	if ok {
 		c.timingWheel.MoveTimer(key, expiry)
 	} else {
diff --git a/core/collection/timingwheel.go b/core/collection/timingwheel.go
index 9b74e757..38953c8b 100644
--- a/core/collection/timingwheel.go
+++ b/core/collection/timingwheel.go
@@ -2,6 +2,7 @@ package collection
 
 import (
 	"container/list"
+	"errors"
 	"fmt"
 	"time"
 
@@ -12,6 +13,11 @@ import (
 
 const drainWorkers = 8
 
+var (
+	ErrClosed   = errors.New("TimingWheel is closed already")
+	ErrArgument = errors.New("incorrect task argument")
+)
+
 type (
 	// Execute defines the method to execute the task.
 	Execute func(key, value interface{})
@@ -89,47 +95,67 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
 }
 
 // Drain drains all items and executes them.
-func (tw *TimingWheel) Drain(fn func(key, value interface{})) {
-	tw.drainChannel <- fn
+func (tw *TimingWheel) Drain(fn func(key, value interface{})) error {
+	select {
+	case tw.drainChannel <- fn:
+		return nil
+	case <-tw.stopChannel:
+		return ErrClosed
+	}
 }
 
 // MoveTimer moves the task with the given key to the given delay.
-func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) {
+func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error {
 	if delay <= 0 || key == nil {
-		return
+		return ErrArgument
 	}
 
-	tw.moveChannel <- baseEntry{
+	select {
+	case tw.moveChannel <- baseEntry{
 		delay: delay,
 		key:   key,
+	}:
+		return nil
+	case <-tw.stopChannel:
+		return ErrClosed
 	}
 }
 
 // RemoveTimer removes the task with the given key.
-func (tw *TimingWheel) RemoveTimer(key interface{}) {
+func (tw *TimingWheel) RemoveTimer(key interface{}) error {
 	if key == nil {
-		return
+		return ErrArgument
 	}
 
-	tw.removeChannel <- key
+	select {
+	case tw.removeChannel <- key:
+		return nil
+	case <-tw.stopChannel:
+		return ErrClosed
+	}
 }
 
 // SetTimer sets the task value with the given key to the delay.
-func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) {
+func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) error {
 	if delay <= 0 || key == nil {
-		return
+		return ErrArgument
 	}
 
-	tw.setChannel <- timingEntry{
+	select {
+	case tw.setChannel <- timingEntry{
 		baseEntry: baseEntry{
 			delay: delay,
 			key:   key,
 		},
 		value: value,
+	}:
+		return nil
+	case <-tw.stopChannel:
+		return ErrClosed
 	}
 }
 
-// Stop stops tw.
+// Stop stops tw. No more actions after stopping a TimingWheel.
 func (tw *TimingWheel) Stop() {
 	close(tw.stopChannel)
 }
diff --git a/core/stores/kv/store.go b/core/stores/kv/store.go
index d39334cc..f7ee6b42 100644
--- a/core/stores/kv/store.go
+++ b/core/stores/kv/store.go
@@ -1,6 +1,7 @@
 package kv
 
 import (
+	"context"
 	"errors"
 	"log"
 
@@ -17,69 +18,133 @@ type (
 	// Store interface represents a KV store.
 	Store interface {
 		Decr(key string) (int64, error)
-		Decrby(key string, increment int64) (int64, error)
+		DecrCtx(ctx context.Context, key string) (int64, error)
+		Decrby(key string, decrement int64) (int64, error)
+		DecrbyCtx(ctx context.Context, key string, decrement int64) (int64, error)
 		Del(keys ...string) (int, error)
+		DelCtx(ctx context.Context, keys ...string) (int, error)
 		Eval(script, key string, args ...interface{}) (interface{}, error)
+		EvalCtx(ctx context.Context, script, key string, args ...interface{}) (interface{}, error)
 		Exists(key string) (bool, error)
+		ExistsCtx(ctx context.Context, key string) (bool, error)
 		Expire(key string, seconds int) error
+		ExpireCtx(ctx context.Context, key string, seconds int) error
 		Expireat(key string, expireTime int64) error
+		ExpireatCtx(ctx context.Context, key string, expireTime int64) error
 		Get(key string) (string, error)
+		GetCtx(ctx context.Context, key string) (string, error)
 		GetSet(key, value string) (string, error)
+		GetSetCtx(ctx context.Context, key, value string) (string, error)
 		Hdel(key, field string) (bool, error)
+		HdelCtx(ctx context.Context, key, field string) (bool, error)
 		Hexists(key, field string) (bool, error)
+		HexistsCtx(ctx context.Context, key, field string) (bool, error)
 		Hget(key, field string) (string, error)
+		HgetCtx(ctx context.Context, key, field string) (string, error)
 		Hgetall(key string) (map[string]string, error)
+		HgetallCtx(ctx context.Context, key string) (map[string]string, error)
 		Hincrby(key, field string, increment int) (int, error)
+		HincrbyCtx(ctx context.Context, key, field string, increment int) (int, error)
 		Hkeys(key string) ([]string, error)
+		HkeysCtx(ctx context.Context, key string) ([]string, error)
 		Hlen(key string) (int, error)
+		HlenCtx(ctx context.Context, key string) (int, error)
 		Hmget(key string, fields ...string) ([]string, error)
+		HmgetCtx(ctx context.Context, key string, fields ...string) ([]string, error)
 		Hset(key, field, value string) error
+		HsetCtx(ctx context.Context, key, field, value string) error
 		Hsetnx(key, field, value string) (bool, error)
+		HsetnxCtx(ctx context.Context, key, field, value string) (bool, error)
 		Hmset(key string, fieldsAndValues map[string]string) error
+		HmsetCtx(ctx context.Context, key string, fieldsAndValues map[string]string) error
 		Hvals(key string) ([]string, error)
+		HvalsCtx(ctx context.Context, key string) ([]string, error)
 		Incr(key string) (int64, error)
+		IncrCtx(ctx context.Context, key string) (int64, error)
 		Incrby(key string, increment int64) (int64, error)
+		IncrbyCtx(ctx context.Context, key string, increment int64) (int64, error)
 		Lindex(key string, index int64) (string, error)
+		LindexCtx(ctx context.Context, key string, index int64) (string, error)
 		Llen(key string) (int, error)
+		LlenCtx(ctx context.Context, key string) (int, error)
 		Lpop(key string) (string, error)
+		LpopCtx(ctx context.Context, key string) (string, error)
 		Lpush(key string, values ...interface{}) (int, error)
+		LpushCtx(ctx context.Context, key string, values ...interface{}) (int, error)
 		Lrange(key string, start, stop int) ([]string, error)
+		LrangeCtx(ctx context.Context, key string, start, stop int) ([]string, error)
 		Lrem(key string, count int, value string) (int, error)
+		LremCtx(ctx context.Context, key string, count int, value string) (int, error)
 		Persist(key string) (bool, error)
+		PersistCtx(ctx context.Context, key string) (bool, error)
 		Pfadd(key string, values ...interface{}) (bool, error)
+		PfaddCtx(ctx context.Context, key string, values ...interface{}) (bool, error)
 		Pfcount(key string) (int64, error)
+		PfcountCtx(ctx context.Context, key string) (int64, error)
 		Rpush(key string, values ...interface{}) (int, error)
+		RpushCtx(ctx context.Context, key string, values ...interface{}) (int, error)
 		Sadd(key string, values ...interface{}) (int, error)
+		SaddCtx(ctx context.Context, key string, values ...interface{}) (int, error)
 		Scard(key string) (int64, error)
+		ScardCtx(ctx context.Context, key string) (int64, error)
 		Set(key, value string) error
+		SetCtx(ctx context.Context, key, value string) error
 		Setex(key, value string, seconds int) error
+		SetexCtx(ctx context.Context, key, value string, seconds int) error
 		Setnx(key, value string) (bool, error)
+		SetnxCtx(ctx context.Context, key, value string) (bool, error)
 		SetnxEx(key, value string, seconds int) (bool, error)
+		SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error)
 		Sismember(key string, value interface{}) (bool, error)
+		SismemberCtx(ctx context.Context, key string, value interface{}) (bool, error)
 		Smembers(key string) ([]string, error)
+		SmembersCtx(ctx context.Context, key string) ([]string, error)
 		Spop(key string) (string, error)
+		SpopCtx(ctx context.Context, key string) (string, error)
 		Srandmember(key string, count int) ([]string, error)
+		SrandmemberCtx(ctx context.Context, key string, count int) ([]string, error)
 		Srem(key string, values ...interface{}) (int, error)
+		SremCtx(ctx context.Context, key string, values ...interface{}) (int, error)
 		Sscan(key string, cursor uint64, match string, count int64) (keys []string, cur uint64, err error)
+		SscanCtx(ctx context.Context, key string, cursor uint64, match string, count int64) (keys []string, cur uint64, err error)
 		Ttl(key string) (int, error)
+		TtlCtx(ctx context.Context, key string) (int, error)
 		Zadd(key string, score int64, value string) (bool, error)
+		ZaddCtx(ctx context.Context, key string, score int64, value string) (bool, error)
 		Zadds(key string, ps ...redis.Pair) (int64, error)
+		ZaddsCtx(ctx context.Context, key string, ps ...redis.Pair) (int64, error)
 		Zcard(key string) (int, error)
+		ZcardCtx(ctx context.Context, key string) (int, error)
 		Zcount(key string, start, stop int64) (int, error)
+		ZcountCtx(ctx context.Context, key string, start, stop int64) (int, error)
 		Zincrby(key string, increment int64, field string) (int64, error)
+		ZincrbyCtx(ctx context.Context, key string, increment int64, field string) (int64, error)
 		Zrange(key string, start, stop int64) ([]string, error)
+		ZrangeCtx(ctx context.Context, key string, start, stop int64) ([]string, error)
 		ZrangeWithScores(key string, start, stop int64) ([]redis.Pair, error)
+		ZrangeWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error)
 		ZrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
+		ZrangebyscoreWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error)
 		ZrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
+		ZrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key string, start, stop int64, page, size int) ([]redis.Pair, error)
 		Zrank(key, field string) (int64, error)
+		ZrankCtx(ctx context.Context, key, field string) (int64, error)
 		Zrem(key string, values ...interface{}) (int, error)
+		ZremCtx(ctx context.Context, key string, values ...interface{}) (int, error)
 		Zremrangebyrank(key string, start, stop int64) (int, error)
+		ZremrangebyrankCtx(ctx context.Context, key string, start, stop int64) (int, error)
 		Zremrangebyscore(key string, start, stop int64) (int, error)
+		ZremrangebyscoreCtx(ctx context.Context, key string, start, stop int64) (int, error)
 		Zrevrange(key string, start, stop int64) ([]string, error)
+		ZrevrangeCtx(ctx context.Context, key string, start, stop int64) ([]string, error)
 		ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
+		ZrevrangebyscoreWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error)
 		ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
+		ZrevrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key string, start, stop int64, page, size int) ([]redis.Pair, error)
 		Zscore(key, value string) (int64, error)
+		ZscoreCtx(ctx context.Context, key, value string) (int64, error)
 		Zrevrank(key, field string) (int64, error)
+		ZrevrankCtx(ctx context.Context, key, field string) (int64, error)
 	}
 
 	clusterStore struct {
@@ -107,24 +172,36 @@ func NewStore(c KvConf) Store {
 }
 
 func (cs clusterStore) Decr(key string) (int64, error) {
+	return cs.DecrCtx(context.Background(), key)
+}
+
+func (cs clusterStore) DecrCtx(ctx context.Context, key string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Decr(key)
+	return node.DecrCtx(ctx, key)
 }
 
-func (cs clusterStore) Decrby(key string, increment int64) (int64, error) {
+func (cs clusterStore) Decrby(key string, decrement int64) (int64, error) {
+	return cs.DecrbyCtx(context.Background(), key, decrement)
+}
+
+func (cs clusterStore) DecrbyCtx(ctx context.Context, key string, decrement int64) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Decrby(key, increment)
+	return node.DecrbyCtx(ctx, key, decrement)
 }
 
 func (cs clusterStore) Del(keys ...string) (int, error) {
+	return cs.DelCtx(context.Background(), keys...)
+}
+
+func (cs clusterStore) DelCtx(ctx context.Context, keys ...string) (int, error) {
 	var val int
 	var be errorx.BatchError
 
@@ -135,7 +212,7 @@ func (cs clusterStore) Del(keys ...string) (int, error) {
 			continue
 		}
 
-		if v, e := node.Del(key); e != nil {
+		if v, e := node.DelCtx(ctx, key); e != nil {
 			be.Add(e)
 		} else {
 			val += v
@@ -146,555 +223,802 @@ func (cs clusterStore) Del(keys ...string) (int, error) {
 }
 
 func (cs clusterStore) Eval(script, key string, args ...interface{}) (interface{}, error) {
+	return cs.EvalCtx(context.Background(), script, key, args...)
+}
+
+func (cs clusterStore) EvalCtx(ctx context.Context, script, key string, args ...interface{}) (interface{}, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Eval(script, []string{key}, args...)
+	return node.EvalCtx(ctx, script, []string{key}, args...)
 }
 
 func (cs clusterStore) Exists(key string) (bool, error) {
+	return cs.ExistsCtx(context.Background(), key)
+}
+
+func (cs clusterStore) ExistsCtx(ctx context.Context, key string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Exists(key)
+	return node.ExistsCtx(ctx, key)
 }
 
 func (cs clusterStore) Expire(key string, seconds int) error {
+	return cs.ExpireCtx(context.Background(), key, seconds)
+}
+
+func (cs clusterStore) ExpireCtx(ctx context.Context, key string, seconds int) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Expire(key, seconds)
+	return node.ExpireCtx(ctx, key, seconds)
 }
 
 func (cs clusterStore) Expireat(key string, expireTime int64) error {
+	return cs.ExpireatCtx(context.Background(), key, expireTime)
+}
+
+func (cs clusterStore) ExpireatCtx(ctx context.Context, key string, expireTime int64) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Expireat(key, expireTime)
+	return node.ExpireatCtx(ctx, key, expireTime)
 }
 
 func (cs clusterStore) Get(key string) (string, error) {
+	return cs.GetCtx(context.Background(), key)
+}
+
+func (cs clusterStore) GetCtx(ctx context.Context, key string) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.Get(key)
+	return node.GetCtx(ctx, key)
 }
 
 func (cs clusterStore) Hdel(key, field string) (bool, error) {
+	return cs.HdelCtx(context.Background(), key, field)
+}
+
+func (cs clusterStore) HdelCtx(ctx context.Context, key, field string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Hdel(key, field)
+	return node.HdelCtx(ctx, key, field)
 }
 
 func (cs clusterStore) Hexists(key, field string) (bool, error) {
+	return cs.HexistsCtx(context.Background(), key, field)
+}
+
+func (cs clusterStore) HexistsCtx(ctx context.Context, key, field string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Hexists(key, field)
+	return node.HexistsCtx(ctx, key, field)
 }
 
 func (cs clusterStore) Hget(key, field string) (string, error) {
+	return cs.HgetCtx(context.Background(), key, field)
+}
+
+func (cs clusterStore) HgetCtx(ctx context.Context, key, field string) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.Hget(key, field)
+	return node.HgetCtx(ctx, key, field)
 }
 
 func (cs clusterStore) Hgetall(key string) (map[string]string, error) {
+	return cs.HgetallCtx(context.Background(), key)
+}
+
+func (cs clusterStore) HgetallCtx(ctx context.Context, key string) (map[string]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Hgetall(key)
+	return node.HgetallCtx(ctx, key)
 }
 
 func (cs clusterStore) Hincrby(key, field string, increment int) (int, error) {
+	return cs.HincrbyCtx(context.Background(), key, field, increment)
+}
+
+func (cs clusterStore) HincrbyCtx(ctx context.Context, key, field string, increment int) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Hincrby(key, field, increment)
+	return node.HincrbyCtx(ctx, key, field, increment)
 }
 
 func (cs clusterStore) Hkeys(key string) ([]string, error) {
+	return cs.HkeysCtx(context.Background(), key)
+}
+
+func (cs clusterStore) HkeysCtx(ctx context.Context, key string) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Hkeys(key)
+	return node.HkeysCtx(ctx, key)
 }
 
 func (cs clusterStore) Hlen(key string) (int, error) {
+	return cs.HlenCtx(context.Background(), key)
+}
+
+func (cs clusterStore) HlenCtx(ctx context.Context, key string) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Hlen(key)
+	return node.HlenCtx(ctx, key)
 }
 
 func (cs clusterStore) Hmget(key string, fields ...string) ([]string, error) {
+	return cs.HmgetCtx(context.Background(), key, fields...)
+}
+
+func (cs clusterStore) HmgetCtx(ctx context.Context, key string, fields ...string) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Hmget(key, fields...)
+	return node.HmgetCtx(ctx, key, fields...)
 }
 
 func (cs clusterStore) Hset(key, field, value string) error {
+	return cs.HsetCtx(context.Background(), key, field, value)
+}
+
+func (cs clusterStore) HsetCtx(ctx context.Context, key, field, value string) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Hset(key, field, value)
+	return node.HsetCtx(ctx, key, field, value)
 }
 
 func (cs clusterStore) Hsetnx(key, field, value string) (bool, error) {
+	return cs.HsetnxCtx(context.Background(), key, field, value)
+}
+
+func (cs clusterStore) HsetnxCtx(ctx context.Context, key, field, value string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Hsetnx(key, field, value)
+	return node.HsetnxCtx(ctx, key, field, value)
 }
 
 func (cs clusterStore) Hmset(key string, fieldsAndValues map[string]string) error {
+	return cs.HmsetCtx(context.Background(), key, fieldsAndValues)
+}
+
+func (cs clusterStore) HmsetCtx(ctx context.Context, key string, fieldsAndValues map[string]string) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Hmset(key, fieldsAndValues)
+	return node.HmsetCtx(ctx, key, fieldsAndValues)
 }
 
 func (cs clusterStore) Hvals(key string) ([]string, error) {
+	return cs.HvalsCtx(context.Background(), key)
+}
+
+func (cs clusterStore) HvalsCtx(ctx context.Context, key string) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Hvals(key)
+	return node.HvalsCtx(ctx, key)
 }
 
 func (cs clusterStore) Incr(key string) (int64, error) {
+	return cs.IncrCtx(context.Background(), key)
+}
+
+func (cs clusterStore) IncrCtx(ctx context.Context, key string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Incr(key)
+	return node.IncrCtx(ctx, key)
 }
 
 func (cs clusterStore) Incrby(key string, increment int64) (int64, error) {
+	return cs.IncrbyCtx(context.Background(), key, increment)
+}
+
+func (cs clusterStore) IncrbyCtx(ctx context.Context, key string, increment int64) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Incrby(key, increment)
+	return node.IncrbyCtx(ctx, key, increment)
 }
 
 func (cs clusterStore) Llen(key string) (int, error) {
+	return cs.LlenCtx(context.Background(), key)
+}
+
+func (cs clusterStore) LlenCtx(ctx context.Context, key string) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Llen(key)
+	return node.LlenCtx(ctx, key)
 }
 
 func (cs clusterStore) Lindex(key string, index int64) (string, error) {
+	return cs.LindexCtx(context.Background(), key, index)
+}
+
+func (cs clusterStore) LindexCtx(ctx context.Context, key string, index int64) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.Lindex(key, index)
+	return node.LindexCtx(ctx, key, index)
 }
 
 func (cs clusterStore) Lpop(key string) (string, error) {
+	return cs.LpopCtx(context.Background(), key)
+}
+
+func (cs clusterStore) LpopCtx(ctx context.Context, key string) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.Lpop(key)
+	return node.LpopCtx(ctx, key)
 }
 
 func (cs clusterStore) Lpush(key string, values ...interface{}) (int, error) {
+	return cs.LpushCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) LpushCtx(ctx context.Context, key string, values ...interface{}) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Lpush(key, values...)
+	return node.LpushCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Lrange(key string, start, stop int) ([]string, error) {
+	return cs.LrangeCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) LrangeCtx(ctx context.Context, key string, start, stop int) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Lrange(key, start, stop)
+	return node.LrangeCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) Lrem(key string, count int, value string) (int, error) {
+	return cs.LremCtx(context.Background(), key, count, value)
+}
+
+func (cs clusterStore) LremCtx(ctx context.Context, key string, count int, value string) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Lrem(key, count, value)
+	return node.LremCtx(ctx, key, count, value)
 }
 
 func (cs clusterStore) Persist(key string) (bool, error) {
+	return cs.PersistCtx(context.Background(), key)
+}
+
+func (cs clusterStore) PersistCtx(ctx context.Context, key string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Persist(key)
+	return node.PersistCtx(ctx, key)
 }
 
 func (cs clusterStore) Pfadd(key string, values ...interface{}) (bool, error) {
+	return cs.PfaddCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) PfaddCtx(ctx context.Context, key string, values ...interface{}) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Pfadd(key, values...)
+	return node.PfaddCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Pfcount(key string) (int64, error) {
+	return cs.PfcountCtx(context.Background(), key)
+}
+
+func (cs clusterStore) PfcountCtx(ctx context.Context, key string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Pfcount(key)
+	return node.PfcountCtx(ctx, key)
 }
 
 func (cs clusterStore) Rpush(key string, values ...interface{}) (int, error) {
+	return cs.RpushCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) RpushCtx(ctx context.Context, key string, values ...interface{}) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Rpush(key, values...)
+	return node.RpushCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Sadd(key string, values ...interface{}) (int, error) {
+	return cs.SaddCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) SaddCtx(ctx context.Context, key string, values ...interface{}) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Sadd(key, values...)
+	return node.SaddCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Scard(key string) (int64, error) {
+	return cs.ScardCtx(context.Background(), key)
+}
+
+func (cs clusterStore) ScardCtx(ctx context.Context, key string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Scard(key)
+	return node.ScardCtx(ctx, key)
 }
 
 func (cs clusterStore) Set(key, value string) error {
+	return cs.SetCtx(context.Background(), key, value)
+}
+
+func (cs clusterStore) SetCtx(ctx context.Context, key, value string) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Set(key, value)
+	return node.SetCtx(ctx, key, value)
 }
 
 func (cs clusterStore) Setex(key, value string, seconds int) error {
+	return cs.SetexCtx(context.Background(), key, value, seconds)
+}
+
+func (cs clusterStore) SetexCtx(ctx context.Context, key, value string, seconds int) error {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return err
 	}
 
-	return node.Setex(key, value, seconds)
+	return node.SetexCtx(ctx, key, value, seconds)
 }
 
 func (cs clusterStore) Setnx(key, value string) (bool, error) {
+	return cs.SetnxCtx(context.Background(), key, value)
+}
+
+func (cs clusterStore) SetnxCtx(ctx context.Context, key, value string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Setnx(key, value)
+	return node.SetnxCtx(ctx, key, value)
 }
 
 func (cs clusterStore) SetnxEx(key, value string, seconds int) (bool, error) {
+	return cs.SetnxExCtx(context.Background(), key, value, seconds)
+}
+
+func (cs clusterStore) SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.SetnxEx(key, value, seconds)
+	return node.SetnxExCtx(ctx, key, value, seconds)
 }
 
 func (cs clusterStore) GetSet(key, value string) (string, error) {
+	return cs.GetSetCtx(context.Background(), key, value)
+}
+
+func (cs clusterStore) GetSetCtx(ctx context.Context, key, value string) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.GetSet(key, value)
+	return node.GetSetCtx(ctx, key, value)
 }
 
 func (cs clusterStore) Sismember(key string, value interface{}) (bool, error) {
+	return cs.SismemberCtx(context.Background(), key, value)
+}
+
+func (cs clusterStore) SismemberCtx(ctx context.Context, key string, value interface{}) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Sismember(key, value)
+	return node.SismemberCtx(ctx, key, value)
 }
 
 func (cs clusterStore) Smembers(key string) ([]string, error) {
+	return cs.SmembersCtx(context.Background(), key)
+}
+
+func (cs clusterStore) SmembersCtx(ctx context.Context, key string) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Smembers(key)
+	return node.SmembersCtx(ctx, key)
 }
 
 func (cs clusterStore) Spop(key string) (string, error) {
+	return cs.SpopCtx(context.Background(), key)
+}
+
+func (cs clusterStore) SpopCtx(ctx context.Context, key string) (string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return "", err
 	}
 
-	return node.Spop(key)
+	return node.SpopCtx(ctx, key)
 }
 
 func (cs clusterStore) Srandmember(key string, count int) ([]string, error) {
+	return cs.SrandmemberCtx(context.Background(), key, count)
+}
+
+func (cs clusterStore) SrandmemberCtx(ctx context.Context, key string, count int) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Srandmember(key, count)
+	return node.SrandmemberCtx(ctx, key, count)
 }
 
 func (cs clusterStore) Srem(key string, values ...interface{}) (int, error) {
+	return cs.SremCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) SremCtx(ctx context.Context, key string, values ...interface{}) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Srem(key, values...)
+	return node.SremCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Sscan(key string, cursor uint64, match string, count int64) (
+	keys []string, cur uint64, err error) {
+	return cs.SscanCtx(context.Background(), key, cursor, match, count)
+}
+
+func (cs clusterStore) SscanCtx(ctx context.Context, key string, cursor uint64, match string, count int64) (
 	keys []string, cur uint64, err error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, 0, err
 	}
 
-	return node.Sscan(key, cursor, match, count)
+	return node.SscanCtx(ctx, key, cursor, match, count)
 }
 
 func (cs clusterStore) Ttl(key string) (int, error) {
+	return cs.TtlCtx(context.Background(), key)
+}
+
+func (cs clusterStore) TtlCtx(ctx context.Context, key string) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Ttl(key)
+	return node.TtlCtx(ctx, key)
 }
 
 func (cs clusterStore) Zadd(key string, score int64, value string) (bool, error) {
+	return cs.ZaddCtx(context.Background(), key, score, value)
+}
+
+func (cs clusterStore) ZaddCtx(ctx context.Context, key string, score int64, value string) (bool, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return false, err
 	}
 
-	return node.Zadd(key, score, value)
+	return node.ZaddCtx(ctx, key, score, value)
 }
 
 func (cs clusterStore) Zadds(key string, ps ...redis.Pair) (int64, error) {
+	return cs.ZaddsCtx(context.Background(), key, ps...)
+}
+
+func (cs clusterStore) ZaddsCtx(ctx context.Context, key string, ps ...redis.Pair) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zadds(key, ps...)
+	return node.ZaddsCtx(ctx, key, ps...)
 }
 
 func (cs clusterStore) Zcard(key string) (int, error) {
+	return cs.ZcardCtx(context.Background(), key)
+}
+
+func (cs clusterStore) ZcardCtx(ctx context.Context, key string) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zcard(key)
+	return node.ZcardCtx(ctx, key)
 }
 
 func (cs clusterStore) Zcount(key string, start, stop int64) (int, error) {
+	return cs.ZcountCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZcountCtx(ctx context.Context, key string, start, stop int64) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zcount(key, start, stop)
+	return node.ZcountCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) Zincrby(key string, increment int64, field string) (int64, error) {
+	return cs.ZincrbyCtx(context.Background(), key, increment, field)
+}
+
+func (cs clusterStore) ZincrbyCtx(ctx context.Context, key string, increment int64, field string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zincrby(key, increment, field)
+	return node.ZincrbyCtx(ctx, key, increment, field)
 }
 
 func (cs clusterStore) Zrank(key, field string) (int64, error) {
+	return cs.ZrankCtx(context.Background(), key, field)
+}
+
+func (cs clusterStore) ZrankCtx(ctx context.Context, key, field string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zrank(key, field)
+	return node.ZrankCtx(ctx, key, field)
 }
 
 func (cs clusterStore) Zrange(key string, start, stop int64) ([]string, error) {
+	return cs.ZrangeCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZrangeCtx(ctx context.Context, key string, start, stop int64) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Zrange(key, start, stop)
+	return node.ZrangeCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) ZrangeWithScores(key string, start, stop int64) ([]redis.Pair, error) {
+	return cs.ZrangeWithScoresCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZrangeWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.ZrangeWithScores(key, start, stop)
+	return node.ZrangeWithScoresCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) ZrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error) {
+	return cs.ZrangebyscoreWithScoresCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZrangebyscoreWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.ZrangebyscoreWithScores(key, start, stop)
+	return node.ZrangebyscoreWithScoresCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) ZrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) (
+	[]redis.Pair, error) {
+	return cs.ZrangebyscoreWithScoresAndLimitCtx(context.Background(), key, start, stop, page, size)
+}
+
+func (cs clusterStore) ZrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key string, start, stop int64, page, size int) (
 	[]redis.Pair, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.ZrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
+	return node.ZrangebyscoreWithScoresAndLimitCtx(ctx, key, start, stop, page, size)
 }
 
 func (cs clusterStore) Zrem(key string, values ...interface{}) (int, error) {
+	return cs.ZremCtx(context.Background(), key, values...)
+}
+
+func (cs clusterStore) ZremCtx(ctx context.Context, key string, values ...interface{}) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zrem(key, values...)
+	return node.ZremCtx(ctx, key, values...)
 }
 
 func (cs clusterStore) Zremrangebyrank(key string, start, stop int64) (int, error) {
+	return cs.ZremrangebyrankCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZremrangebyrankCtx(ctx context.Context, key string, start, stop int64) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zremrangebyrank(key, start, stop)
+	return node.ZremrangebyrankCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) Zremrangebyscore(key string, start, stop int64) (int, error) {
+	return cs.ZremrangebyscoreCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZremrangebyscoreCtx(ctx context.Context, key string, start, stop int64) (int, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zremrangebyscore(key, start, stop)
+	return node.ZremrangebyscoreCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) Zrevrange(key string, start, stop int64) ([]string, error) {
+	return cs.ZrevrangeCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZrevrangeCtx(ctx context.Context, key string, start, stop int64) ([]string, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.Zrevrange(key, start, stop)
+	return node.ZrevrangeCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error) {
+	return cs.ZrevrangebyscoreWithScoresCtx(context.Background(), key, start, stop)
+}
+
+func (cs clusterStore) ZrevrangebyscoreWithScoresCtx(ctx context.Context, key string, start, stop int64) ([]redis.Pair, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.ZrevrangebyscoreWithScores(key, start, stop)
+	return node.ZrevrangebyscoreWithScoresCtx(ctx, key, start, stop)
 }
 
 func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) (
+	[]redis.Pair, error) {
+	return cs.ZrevrangebyscoreWithScoresAndLimitCtx(context.Background(), key, start, stop, page, size)
+}
+
+func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimitCtx(ctx context.Context, key string, start, stop int64, page, size int) (
 	[]redis.Pair, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
+	return node.ZrevrangebyscoreWithScoresAndLimitCtx(ctx, key, start, stop, page, size)
 }
 
 func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
+	return cs.ZrevrankCtx(context.Background(), key, field)
+}
+
+func (cs clusterStore) ZrevrankCtx(ctx context.Context, key, field string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zrevrank(key, field)
+	return node.ZrevrankCtx(ctx, key, field)
 }
 
 func (cs clusterStore) Zscore(key, value string) (int64, error) {
+	return cs.ZscoreCtx(context.Background(), key, value)
+}
+
+func (cs clusterStore) ZscoreCtx(ctx context.Context, key, value string) (int64, error) {
 	node, err := cs.getRedis(key)
 	if err != nil {
 		return 0, err
 	}
 
-	return node.Zscore(key, value)
+	return node.ZscoreCtx(ctx, key, value)
 }
 
 func (cs clusterStore) getRedis(key string) (*redis.Redis, error) {
diff --git a/readme-cn.md b/readme-cn.md
index c8bb9048..5ae5ba1d 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -19,7 +19,7 @@
 > 
 > `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
 > 
-> `goctl migrate —verbose —version v1.3.1`
+> `goctl migrate —verbose —version v1.3.2`
 
 ## 0. go-zero 介绍
 
@@ -116,6 +116,16 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
     
     # Go 1.16 及以后版本
     GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
+
+    # docker for amd64 architecture
+    docker pull kevinwan/goctl
+    # run goctl like
+    docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
+
+    # docker for arm64 (M1) architecture
+    docker pull kevinwan/goctl:latest-arm64
+    # run goctl like
+    docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
     ```
 
     确保 goctl 可执行
diff --git a/readme.md b/readme.md
index 1848d9a6..e441ad99 100644
--- a/readme.md
+++ b/readme.md
@@ -18,7 +18,7 @@ English | [简体中文](readme-cn.md)
 > 
 > `go install github.com/zeromicro/go-zero/tools/goctl@latest`
 > 
-> `goctl migrate —verbose —version v1.3.1`
+> `goctl migrate —verbose —version v1.3.2`
 
 ## 0. what is go-zero
 
@@ -119,6 +119,16 @@ go get -u github.com/zeromicro/go-zero
    
    # for Go 1.16 and later
    go install github.com/zeromicro/go-zero/tools/goctl@latest
+
+   # docker for amd64 architecture
+   docker pull kevinwan/goctl
+   # run goctl like
+   docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
+
+   # docker for arm64 (M1) architecture
+   docker pull kevinwan/goctl:latest-arm64
+   # run goctl like
+   docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
    ```
 
    make sure goctl is executable.
diff --git a/rest/httpc/internal/interceptor.go b/rest/httpc/internal/interceptor.go
index d7171b9f..194604ed 100644
--- a/rest/httpc/internal/interceptor.go
+++ b/rest/httpc/internal/interceptor.go
@@ -4,5 +4,5 @@ import "net/http"
 
 type (
 	Interceptor     func(r *http.Request) (*http.Request, ResponseHandler)
-	ResponseHandler func(*http.Response)
+	ResponseHandler func(resp *http.Response, err error)
 )
diff --git a/rest/httpc/internal/loginterceptor.go b/rest/httpc/internal/loginterceptor.go
index 1e79f153..b004ad91 100644
--- a/rest/httpc/internal/loginterceptor.go
+++ b/rest/httpc/internal/loginterceptor.go
@@ -10,15 +10,21 @@ import (
 
 func LogInterceptor(r *http.Request) (*http.Request, ResponseHandler) {
 	start := timex.Now()
-	return r, func(resp *http.Response) {
+	return r, func(resp *http.Response, err error) {
 		duration := timex.Since(start)
+		if err != nil {
+			logger := logx.WithContext(r.Context()).WithDuration(duration)
+			logger.Errorf("[HTTP] %s %s - %v", r.Method, r.URL, err)
+			return
+		}
+
 		var tc propagation.TraceContext
 		ctx := tc.Extract(r.Context(), propagation.HeaderCarrier(resp.Header))
 		logger := logx.WithContext(ctx).WithDuration(duration)
 		if isOkResponse(resp.StatusCode) {
-			logger.Infof("[HTTP] %d - %s %s/%s", resp.StatusCode, r.Method, r.Host, r.RequestURI)
+			logger.Infof("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
 		} else {
-			logger.Errorf("[HTTP] %d - %s %s/%s", resp.StatusCode, r.Method, r.Host, r.RequestURI)
+			logger.Errorf("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
 		}
 	}
 }
diff --git a/rest/httpc/requests.go b/rest/httpc/requests.go
index 8a67a177..85841769 100644
--- a/rest/httpc/requests.go
+++ b/rest/httpc/requests.go
@@ -1,21 +1,44 @@
 package httpc
 
 import (
-	"io"
 	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpc/internal"
 )
 
-// Do sends an HTTP request to the service assocated with the given key.
-func Do(key string, r *http.Request) (*http.Response, error) {
-	return NewService(key).Do(r)
+var interceptors = []internal.Interceptor{
+	internal.LogInterceptor,
+}
+
+// DoRequest sends an HTTP request and returns an HTTP response.
+func DoRequest(r *http.Request) (*http.Response, error) {
+	return request(r, defaultClient{})
 }
 
-// Get sends an HTTP GET request to the service assocated with the given key.
-func Get(key, url string) (*http.Response, error) {
-	return NewService(key).Get(url)
+type (
+	client interface {
+		do(r *http.Request) (*http.Response, error)
+	}
+
+	defaultClient struct{}
+)
+
+func (c defaultClient) do(r *http.Request) (*http.Response, error) {
+	return http.DefaultClient.Do(r)
 }
 
-// Post sends an HTTP POST request to the service assocated with the given key.
-func Post(key, url, contentType string, body io.Reader) (*http.Response, error) {
-	return NewService(key).Post(url, contentType, body)
+func request(r *http.Request, cli client) (*http.Response, error) {
+	var respHandlers []internal.ResponseHandler
+	for _, interceptor := range interceptors {
+		var h internal.ResponseHandler
+		r, h = interceptor(r)
+		respHandlers = append(respHandlers, h)
+	}
+
+	resp, err := cli.do(r)
+	for i := len(respHandlers) - 1; i >= 0; i-- {
+		respHandlers[i](resp, err)
+	}
+
+	return resp, err
 }
diff --git a/rest/httpc/service.go b/rest/httpc/service.go
index 8297a3df..54164962 100644
--- a/rest/httpc/service.go
+++ b/rest/httpc/service.go
@@ -1,30 +1,19 @@
 package httpc
 
 import (
-	"io"
 	"net/http"
 
 	"github.com/zeromicro/go-zero/core/breaker"
-	"github.com/zeromicro/go-zero/core/logx"
-	"github.com/zeromicro/go-zero/rest/httpc/internal"
 )
 
-var interceptors = []internal.Interceptor{
-	internal.LogInterceptor,
-}
-
 type (
 	// Option is used to customize the *http.Client.
 	Option func(r *http.Request) *http.Request
 
 	// Service represents a remote HTTP service.
 	Service interface {
-		// Do sends an HTTP request to the service.
-		Do(r *http.Request) (*http.Response, error)
-		// Get sends an HTTP GET request to the service.
-		Get(url string) (*http.Response, error)
-		// Post sends an HTTP POST request to the service.
-		Post(url, contentType string, body io.Reader) (*http.Response, error)
+		// DoRequest sends a HTTP request to the service.
+		DoRequest(r *http.Request) (*http.Response, error)
 	}
 
 	namedService struct {
@@ -50,50 +39,12 @@ func NewServiceWithClient(name string, cli *http.Client, opts ...Option) Service
 	}
 }
 
-// Do sends an HTTP request to the service.
-func (s namedService) Do(r *http.Request) (resp *http.Response, err error) {
-	var respHandlers []internal.ResponseHandler
-	for _, interceptor := range interceptors {
-		var h internal.ResponseHandler
-		r, h = interceptor(r)
-		respHandlers = append(respHandlers, h)
-	}
-
-	resp, err = s.doRequest(r)
-	if err != nil {
-		logx.Errorf("[HTTP] %s %s/%s - %v", r.Method, r.Host, r.RequestURI, err)
-		return
-	}
-
-	for i := len(respHandlers) - 1; i >= 0; i-- {
-		respHandlers[i](resp)
-	}
-
-	return
-}
-
-// Get sends an HTTP GET request to the service.
-func (s namedService) Get(url string) (*http.Response, error) {
-	r, err := http.NewRequest(http.MethodGet, url, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	return s.Do(r)
-}
-
-// Post sends an HTTP POST request to the service.
-func (s namedService) Post(url, ctype string, body io.Reader) (*http.Response, error) {
-	r, err := http.NewRequest(http.MethodPost, url, body)
-	if err != nil {
-		return nil, err
-	}
-
-	r.Header.Set(contentType, ctype)
-	return s.Do(r)
+// DoRequest sends an HTTP request to the service.
+func (s namedService) DoRequest(r *http.Request) (*http.Response, error) {
+	return request(r, s)
 }
 
-func (s namedService) doRequest(r *http.Request) (resp *http.Response, err error) {
+func (s namedService) do(r *http.Request) (resp *http.Response, err error) {
 	for _, opt := range s.opts {
 		r = opt(r)
 	}
diff --git a/tools/goctl/Dockerfile b/tools/goctl/Dockerfile
index fe1f3e4c..44c43af2 100644
--- a/tools/goctl/Dockerfile
+++ b/tools/goctl/Dockerfile
@@ -18,7 +18,7 @@ COPY . .
 RUN go build -ldflags="-s -w" -o /app/goctl ./goctl.go
 
 
-FROM alpine
+FROM golang:alpine
 
 RUN apk update --no-cache && apk add --no-cache protoc
 
@@ -29,6 +29,6 @@ COPY --from=builder /go/bin/protoc-gen-go-grpc /usr/bin/protoc-gen-go-grpc
 ENV TZ Asia/Shanghai
 
 WORKDIR /app
-COPY --from=builder /app/goctl /app/goctl
+COPY --from=builder /app/goctl /usr/bin/goctl
 
-CMD ["./goctl"]
+CMD ["goctl"]
diff --git a/tools/goctl/Makefile b/tools/goctl/Makefile
index 54b628b4..14a725e5 100644
--- a/tools/goctl/Makefile
+++ b/tools/goctl/Makefile
@@ -1,12 +1,25 @@
 build:
 	go build -ldflags="-s -w" goctl.go
 	$(if $(shell command -v upx), upx goctl)
+
 mac:
 	GOOS=darwin go build -ldflags="-s -w" -o goctl-darwin goctl.go
 	$(if $(shell command -v upx), upx goctl-darwin)
+
 win:
 	GOOS=windows go build -ldflags="-s -w" -o goctl.exe goctl.go
 	$(if $(shell command -v upx), upx goctl.exe)
+
 linux:
 	GOOS=linux go build -ldflags="-s -w" -o goctl-linux goctl.go
 	$(if $(shell command -v upx), upx goctl-linux)
+
+image:
+	docker build --rm --platform linux/amd64 -t kevinwan/goctl:$(version) .
+	docker tag kevinwan/goctl:$(version) kevinwan/goctl:latest
+	docker push kevinwan/goctl:$(version)
+	docker push kevinwan/goctl:latest
+	docker build --rm --platform linux/arm64 -t kevinwan/goctl:$(version)-arm64 .
+	docker tag kevinwan/goctl:$(version)-arm64 kevinwan/goctl:latest-arm64
+	docker push kevinwan/goctl:$(version)-arm64
+	docker push kevinwan/goctl:latest-arm64
diff --git a/tools/goctl/api/apigen/api.tpl b/tools/goctl/api/apigen/api.tpl
new file mode 100644
index 00000000..2176441c
--- /dev/null
+++ b/tools/goctl/api/apigen/api.tpl
@@ -0,0 +1,24 @@
+syntax = "v1"
+
+info (
+	title: // TODO: add title
+	desc: // TODO: add description
+	author: "{{.gitUser}}"
+	email: "{{.gitEmail}}"
+)
+
+type request {
+	// TODO: add members here and delete this comment
+}
+
+type response {
+	// TODO: add members here and delete this comment
+}
+
+service {{.serviceName}} {
+	@handler GetUser // TODO: set handler name and delete this comment
+	get /users/id/:userId(request) returns(response)
+
+	@handler CreateUser // TODO: set handler name and delete this comment
+	post /users/create(request)
+}
diff --git a/tools/goctl/api/apigen/gen.go b/tools/goctl/api/apigen/gen.go
index afdba15e..e57b603e 100644
--- a/tools/goctl/api/apigen/gen.go
+++ b/tools/goctl/api/apigen/gen.go
@@ -1,11 +1,12 @@
 package apigen
 
 import (
+	_ "embed"
 	"errors"
 	"fmt"
+	"html/template"
 	"path/filepath"
 	"strings"
-	"text/template"
 
 	"github.com/logrusorgru/aurora"
 	"github.com/urfave/cli"
@@ -13,32 +14,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const apiTemplate = `
-syntax = "v1"
-
-info (
-	title: // TODO: add title
-	desc: // TODO: add description
-	author: "{{.gitUser}}"
-	email: "{{.gitEmail}}"
-)
-
-type request {
-	// TODO: add members here and delete this comment
-}
-
-type response {
-	// TODO: add members here and delete this comment
-}
-
-service {{.serviceName}} {
-	@handler GetUser // TODO: set handler name and delete this comment
-	get /users/id/:userId(request) returns(response)
-
-	@handler CreateUser // TODO: set handler name and delete this comment
-	post /users/create(request)
-}
-`
+//go:embed api.tpl
+var apiTemplate string
 
 // ApiCommand create api template file
 func ApiCommand(c *cli.Context) error {
diff --git a/tools/goctl/api/docgen/doc.go b/tools/goctl/api/docgen/doc.go
index 7b876e89..6189d5db 100644
--- a/tools/goctl/api/docgen/doc.go
+++ b/tools/goctl/api/docgen/doc.go
@@ -2,6 +2,7 @@ package docgen
 
 import (
 	"bytes"
+	_ "embed"
 	"fmt"
 	"html/template"
 	"strconv"
@@ -13,25 +14,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/api/util"
 )
 
-const (
-	markdownTemplate = `
-### {{.index}}. {{.routeComment}}
-
-1. 路由定义
-
-- Url: {{.uri}}
-- Method: {{.method}}
-- Request: {{.requestType}}
-- Response: {{.responseType}}
-
-2. 请求定义
-{{.requestContent}}
-
-3. 返回定义
-{{.responseContent}}  
-
-`
-)
+//go:embed markdown.tpl
+var markdownTemplate string
 
 func genDoc(api *spec.ApiSpec, dir, filename string) error {
 	fp, _, err := util.MaybeCreateFile(dir, "", filename)
diff --git a/tools/goctl/api/docgen/markdown.tpl b/tools/goctl/api/docgen/markdown.tpl
new file mode 100644
index 00000000..cc87a2f8
--- /dev/null
+++ b/tools/goctl/api/docgen/markdown.tpl
@@ -0,0 +1,16 @@
+### {{.index}}. {{.routeComment}}
+
+1. route definition
+
+- Url: {{.uri}}
+- Method: {{.method}}
+- Request: {{.requestType}}
+- Response: {{.responseType}}
+
+2. request definition
+
+{{.requestContent}}
+
+3. response definition
+
+{{.responseContent}}
diff --git a/tools/goctl/api/gogen/etc.tpl b/tools/goctl/api/gogen/etc.tpl
new file mode 100644
index 00000000..ed55cf1b
--- /dev/null
+++ b/tools/goctl/api/gogen/etc.tpl
@@ -0,0 +1,3 @@
+Name: {{.serviceName}}
+Host: {{.host}}
+Port: {{.port}}
diff --git a/tools/goctl/api/gogen/genetc.go b/tools/goctl/api/gogen/genetc.go
index c43f7879..12201372 100644
--- a/tools/goctl/api/gogen/genetc.go
+++ b/tools/goctl/api/gogen/genetc.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"strconv"
 
@@ -12,12 +13,11 @@ import (
 const (
 	defaultPort = 8888
 	etcDir      = "etc"
-	etcTemplate = `Name: {{.serviceName}}
-Host: {{.host}}
-Port: {{.port}}
-`
 )
 
+//go:embed etc.tpl
+var etcTemplate string
+
 func genEtc(dir string, cfg *config.Config, api *spec.ApiSpec) error {
 	filename, err := format.FileNamingFormat(cfg.NamingFormat, api.Service.Name)
 	if err != nil {
diff --git a/tools/goctl/api/gogen/genhandlers.go b/tools/goctl/api/gogen/genhandlers.go
index 45b61081..d073bf98 100644
--- a/tools/goctl/api/gogen/genhandlers.go
+++ b/tools/goctl/api/gogen/genhandlers.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"path"
 	"strings"
@@ -14,36 +15,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/vars"
 )
 
-const (
-	defaultLogicPackage = "logic"
-	handlerTemplate     = `package {{.PkgName}}
+const defaultLogicPackage = "logic"
 
-import (
-	"net/http"
-
-	{{if .After1_1_10}}"github.com/zeromicro/go-zero/rest/httpx"{{end}}
-	{{.ImportPackages}}
-)
-
-func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		{{if .HasRequest}}var req types.{{.RequestType}}
-		if err := httpx.Parse(r, &req); err != nil {
-			httpx.Error(w, err)
-			return
-		}
-
-		{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
-		{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
-		if err != nil {
-			httpx.Error(w, err)
-		} else {
-			{{if .HasResp}}httpx.OkJson(w, resp){{else}}httpx.Ok(w){{end}}
-		}
-	}
-}
-`
-)
+//go:embed handler.tpl
+var handlerTemplate string
 
 type handlerInfo struct {
 	PkgName        string
@@ -72,14 +47,10 @@ func genHandler(dir, rootPkg string, cfg *config.Config, group spec.Group, route
 		return err
 	}
 
-	goctlVersion := version.GetGoctlVersion()
-	// todo(anqiansong): This will be removed after a certain number of production versions of goctl (probably 5)
-	after1_1_10 := version.IsVersionGreaterThan(goctlVersion, "1.1.10")
 	return doGenToFile(dir, handler, cfg, group, route, handlerInfo{
 		PkgName:        pkgName,
 		ImportPackages: genHandlerImports(group, route, parentPkg),
 		HandlerName:    handler,
-		After1_1_10:    after1_1_10,
 		RequestType:    util.Title(route.RequestTypeName()),
 		LogicName:      logicName,
 		LogicType:      strings.Title(getLogicName(route)),
diff --git a/tools/goctl/api/gogen/genlogic.go b/tools/goctl/api/gogen/genlogic.go
index 475b941d..28780495 100644
--- a/tools/goctl/api/gogen/genlogic.go
+++ b/tools/goctl/api/gogen/genlogic.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"path"
 	"strconv"
@@ -14,32 +15,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/vars"
 )
 
-const logicTemplate = `package {{.pkgName}}
-
-import (
-	{{.imports}}
-)
-
-type {{.logic}} struct {
-	logx.Logger
-	ctx    context.Context
-	svcCtx *svc.ServiceContext
-}
-
-func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
-	return &{{.logic}}{
-		Logger: logx.WithContext(ctx),
-		ctx:    ctx,
-		svcCtx: svcCtx,
-	}
-}
-
-func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
-	// todo: add your logic here and delete this line
-
-	{{.returnString}}
-}
-`
+//go:embed logic.tpl
+var logicTemplate string
 
 func genLogic(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error {
 	for _, g := range api.Service.Groups {
diff --git a/tools/goctl/api/gogen/genmain.go b/tools/goctl/api/gogen/genmain.go
index 3a3269d0..154a7dcc 100644
--- a/tools/goctl/api/gogen/genmain.go
+++ b/tools/goctl/api/gogen/genmain.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"strings"
 
@@ -11,33 +12,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/vars"
 )
 
-const mainTemplate = `package main
-
-import (
-	"flag"
-	"fmt"
-
-	{{.importPackages}}
-)
-
-var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
-
-func main() {
-	flag.Parse()
-
-	var c config.Config
-	conf.MustLoad(*configFile, &c)
-
-	ctx := svc.NewServiceContext(c)
-	server := rest.MustNewServer(c.RestConf)
-	defer server.Stop()
-
-	handler.RegisterHandlers(server, ctx)
-
-	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
-	server.Start()
-}
-`
+//go:embed main.tpl
+var mainTemplate string
 
 func genMain(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error {
 	name := strings.ToLower(api.Service.Name)
diff --git a/tools/goctl/api/gogen/genmiddleware.go b/tools/goctl/api/gogen/genmiddleware.go
index c283c008..d302b51c 100644
--- a/tools/goctl/api/gogen/genmiddleware.go
+++ b/tools/goctl/api/gogen/genmiddleware.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"strings"
 
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -8,27 +9,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/format"
 )
 
-var middlewareImplementCode = `
-package middleware
-
-import "net/http"
-
-type {{.name}} struct {
-}
-
-func New{{.name}}() *{{.name}} {	
-	return &{{.name}}{}
-}
-
-func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		// TODO generate middleware implement function, delete after code implementation
-
-		// Passthrough to next handler if need 
-		next(w, r)
-	}	
-}
-`
+//go:embed middleware.tpl
+var middlewareImplementCode string
 
 func genMiddleware(dir string, cfg *config.Config, api *spec.ApiSpec) error {
 	middlewares := getMiddleware(api)
diff --git a/tools/goctl/api/gogen/gensvc.go b/tools/goctl/api/gogen/gensvc.go
index 573b8466..1f500a00 100644
--- a/tools/goctl/api/gogen/gensvc.go
+++ b/tools/goctl/api/gogen/gensvc.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"strings"
 
@@ -11,27 +12,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/vars"
 )
 
-const (
-	contextFilename = "service_context"
-	contextTemplate = `package svc
+const contextFilename = "service_context"
 
-import (
-	{{.configImport}}
-)
-
-type ServiceContext struct {
-	Config {{.config}}
-	{{.middleware}}
-}
-
-func NewServiceContext(c {{.config}}) *ServiceContext {
-	return &ServiceContext{
-		Config: c, 
-		{{.middlewareAssignment}}
-	}
-}
-`
-)
+//go:embed svc.tpl
+var contextTemplate string
 
 func genServiceContext(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error {
 	filename, err := format.FileNamingFormat(cfg.NamingFormat, contextFilename)
diff --git a/tools/goctl/api/gogen/gentypes.go b/tools/goctl/api/gogen/gentypes.go
index 6dfe9932..9e91735f 100644
--- a/tools/goctl/api/gogen/gentypes.go
+++ b/tools/goctl/api/gogen/gentypes.go
@@ -1,6 +1,7 @@
 package gogen
 
 import (
+	_ "embed"
 	"fmt"
 	"io"
 	"os"
@@ -14,16 +15,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/format"
 )
 
-const (
-	typesFile     = "types"
-	typesTemplate = `// Code generated by goctl. DO NOT EDIT.
-package types{{if .containsTime}}
-import (
-	"time"
-){{end}}
-{{.types}}
-`
-)
+const typesFile = "types"
+
+//go:embed types.tpl
+var typesTemplate string
 
 // BuildTypes gen types to string
 func BuildTypes(types []spec.Type) (string, error) {
diff --git a/tools/goctl/api/gogen/handler.tpl b/tools/goctl/api/gogen/handler.tpl
new file mode 100644
index 00000000..033dc22e
--- /dev/null
+++ b/tools/goctl/api/gogen/handler.tpl
@@ -0,0 +1,26 @@
+package {{.PkgName}}
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+	{{.ImportPackages}}
+)
+
+func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		{{if .HasRequest}}var req types.{{.RequestType}}
+		if err := httpx.Parse(r, &req); err != nil {
+			httpx.Error(w, err)
+			return
+		}
+
+		{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
+		{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
+		if err != nil {
+			httpx.Error(w, err)
+		} else {
+			{{if .HasResp}}httpx.OkJson(w, resp){{else}}httpx.Ok(w){{end}}
+		}
+	}
+}
diff --git a/tools/goctl/api/gogen/logic.tpl b/tools/goctl/api/gogen/logic.tpl
new file mode 100644
index 00000000..7c043237
--- /dev/null
+++ b/tools/goctl/api/gogen/logic.tpl
@@ -0,0 +1,25 @@
+package {{.pkgName}}
+
+import (
+	{{.imports}}
+)
+
+type {{.logic}} struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
+	return &{{.logic}}{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx,
+	}
+}
+
+func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
+	// todo: add your logic here and delete this line
+
+	{{.returnString}}
+}
diff --git a/tools/goctl/api/gogen/main.tpl b/tools/goctl/api/gogen/main.tpl
new file mode 100644
index 00000000..a4ab692a
--- /dev/null
+++ b/tools/goctl/api/gogen/main.tpl
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+
+	{{.importPackages}}
+)
+
+var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
+
+func main() {
+	flag.Parse()
+
+	var c config.Config
+	conf.MustLoad(*configFile, &c)
+
+	ctx := svc.NewServiceContext(c)
+	server := rest.MustNewServer(c.RestConf)
+	defer server.Stop()
+
+	handler.RegisterHandlers(server, ctx)
+
+	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
+	server.Start()
+}
diff --git a/tools/goctl/api/gogen/middleware.tpl b/tools/goctl/api/gogen/middleware.tpl
new file mode 100644
index 00000000..3a9f8e91
--- /dev/null
+++ b/tools/goctl/api/gogen/middleware.tpl
@@ -0,0 +1,19 @@
+package middleware
+
+import "net/http"
+
+type {{.name}} struct {
+}
+
+func New{{.name}}() *{{.name}} {
+	return &{{.name}}{}
+}
+
+func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		// TODO generate middleware implement function, delete after code implementation
+
+		// Passthrough to next handler if need
+		next(w, r)
+	}
+}
diff --git a/tools/goctl/api/gogen/svc.tpl b/tools/goctl/api/gogen/svc.tpl
new file mode 100644
index 00000000..c15c1e45
--- /dev/null
+++ b/tools/goctl/api/gogen/svc.tpl
@@ -0,0 +1,17 @@
+package svc
+
+import (
+	{{.configImport}}
+)
+
+type ServiceContext struct {
+	Config {{.config}}
+	{{.middleware}}
+}
+
+func NewServiceContext(c {{.config}}) *ServiceContext {
+	return &ServiceContext{
+		Config: c,
+		{{.middlewareAssignment}}
+	}
+}
diff --git a/tools/goctl/api/gogen/types.tpl b/tools/goctl/api/gogen/types.tpl
new file mode 100644
index 00000000..735ec2da
--- /dev/null
+++ b/tools/goctl/api/gogen/types.tpl
@@ -0,0 +1,6 @@
+// Code generated by goctl. DO NOT EDIT.
+package types{{if .containsTime}}
+import (
+	"time"
+){{end}}
+{{.types}}
diff --git a/tools/goctl/api/javagen/bool.tpl b/tools/goctl/api/javagen/bool.tpl
new file mode 100644
index 00000000..ed5c3371
--- /dev/null
+++ b/tools/goctl/api/javagen/bool.tpl
@@ -0,0 +1,9 @@
+
+{{.indent}}{{.decorator}}
+{{.indent}}public {{.returnType}} is{{.property}}() {
+{{.indent}}	return this.{{.tagValue}};
+{{.indent}}}
+
+{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
+{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
+{{.indent}}}
diff --git a/tools/goctl/api/javagen/component.tpl b/tools/goctl/api/javagen/component.tpl
new file mode 100644
index 00000000..0bb309f3
--- /dev/null
+++ b/tools/goctl/api/javagen/component.tpl
@@ -0,0 +1,22 @@
+// Code generated by goctl. DO NOT EDIT.
+package com.xhb.logic.http.packet.{{.packet}}.model;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+{{.imports}}
+
+public class {{.className}} extends {{.superClassName}} {
+
+{{.properties}}
+{{if .HasProperty}}
+
+	public {{.className}}() {
+	}
+
+	public {{.className}}({{.params}}) {
+{{.constructorSetter}}
+	}
+{{end}}
+
+{{.getSet}}
+}
diff --git a/tools/goctl/api/javagen/gen.go b/tools/goctl/api/javagen/gen.go
index e21ffb37..4c99269e 100644
--- a/tools/goctl/api/javagen/gen.go
+++ b/tools/goctl/api/javagen/gen.go
@@ -12,7 +12,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-// JavaCommand the generate java code command entrance
+// JavaCommand generates java code command entrance.
 func JavaCommand(c *cli.Context) error {
 	apiFile := c.String("api")
 	dir := c.String("dir")
diff --git a/tools/goctl/api/javagen/gencomponents.go b/tools/goctl/api/javagen/gencomponents.go
index f87136ea..a9c5a0e1 100644
--- a/tools/goctl/api/javagen/gencomponents.go
+++ b/tools/goctl/api/javagen/gencomponents.go
@@ -3,6 +3,7 @@ package javagen
 import (
 	"bufio"
 	"bytes"
+	_ "embed"
 	"errors"
 	"fmt"
 	"io"
@@ -18,54 +19,19 @@ import (
 )
 
 const (
-	componentTemplate = `// Code generated by goctl. DO NOT EDIT.
-package com.xhb.logic.http.packet.{{.packet}}.model;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-{{.imports}}
-
-public class {{.className}} extends {{.superClassName}} {
-
-{{.properties}}
-{{if .HasProperty}}
-
-	public {{.className}}() {
-	}
-
-	public {{.className}}({{.params}}) {
-{{.constructorSetter}}
-	}
-{{end}}
-
-{{.getSet}}
-}
-`
-	getSetTemplate = `
-{{.indent}}{{.decorator}}
-{{.indent}}public {{.returnType}} get{{.property}}() {
-{{.indent}}	return this.{{.tagValue}};
-{{.indent}}}
-
-{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
-{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
-{{.indent}}}
-`
-
-	boolTemplate = `
-{{.indent}}{{.decorator}}
-{{.indent}}public {{.returnType}} is{{.property}}() {
-{{.indent}}	return this.{{.tagValue}};
-{{.indent}}}
-
-{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
-{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
-{{.indent}}}
-`
 	httpResponseData = "import com.xhb.core.response.HttpResponseData;"
 	httpData         = "import com.xhb.core.packet.HttpData;"
 )
 
+var (
+	//go:embed component.tpl
+	componentTemplate string
+	//go:embed getset.tpl
+	getSetTemplate string
+	//go:embed bool.tpl
+	boolTemplate string
+)
+
 type componentsContext struct {
 	api           *spec.ApiSpec
 	requestTypes  []spec.Type
diff --git a/tools/goctl/api/javagen/genpacket.go b/tools/goctl/api/javagen/genpacket.go
index 7485a348..b27d5710 100644
--- a/tools/goctl/api/javagen/genpacket.go
+++ b/tools/goctl/api/javagen/genpacket.go
@@ -2,6 +2,7 @@ package javagen
 
 import (
 	"bytes"
+	_ "embed"
 	"fmt"
 	"strings"
 	"text/template"
@@ -12,32 +13,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util"
 )
 
-const packetTemplate = `package com.xhb.logic.http.packet.{{.packet}};
-
-import com.xhb.core.packet.HttpPacket;
-import com.xhb.core.network.HttpRequestClient;
-{{.imports}}
-
-{{.doc}}
-public class {{.packetName}} extends HttpPacket<{{.responseType}}> {
-	{{.paramsDeclaration}}
-
-	public {{.packetName}}({{.params}}{{if .HasRequestBody}}{{.requestType}} request{{end}}) {
-		{{if .HasRequestBody}}super(request);{{else}}super(EmptyRequest.instance);{{end}}
-		{{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSetter}}
-    }
-
-	@Override
-    public HttpRequestClient.Method requestMethod() {
-        return HttpRequestClient.Method.{{.method}};
-    }
-
-	@Override
-    public String requestUri() {
-        return {{.uri}};
-    }
-}
-`
+//go:embed packet.tpl
+var packetTemplate string
 
 func genPacket(dir, packetName string, api *spec.ApiSpec) error {
 	for _, route := range api.Service.Routes() {
diff --git a/tools/goctl/api/javagen/getset.tpl b/tools/goctl/api/javagen/getset.tpl
new file mode 100644
index 00000000..c0fac211
--- /dev/null
+++ b/tools/goctl/api/javagen/getset.tpl
@@ -0,0 +1,9 @@
+
+{{.indent}}{{.decorator}}
+{{.indent}}public {{.returnType}} get{{.property}}() {
+{{.indent}}	return this.{{.tagValue}};
+{{.indent}}}
+
+{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
+{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
+{{.indent}}}
diff --git a/tools/goctl/api/javagen/packet.tpl b/tools/goctl/api/javagen/packet.tpl
new file mode 100644
index 00000000..db01bfe3
--- /dev/null
+++ b/tools/goctl/api/javagen/packet.tpl
@@ -0,0 +1,25 @@
+package com.xhb.logic.http.packet.{{.packet}};
+
+import com.xhb.core.packet.HttpPacket;
+import com.xhb.core.network.HttpRequestClient;
+{{.imports}}
+
+{{.doc}}
+public class {{.packetName}} extends HttpPacket<{{.responseType}}> {
+	{{.paramsDeclaration}}
+
+	public {{.packetName}}({{.params}}{{if .HasRequestBody}}{{.requestType}} request{{end}}) {
+		{{if .HasRequestBody}}super(request);{{else}}super(EmptyRequest.instance);{{end}}
+		{{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSetter}}
+    }
+
+	@Override
+    public HttpRequestClient.Method requestMethod() {
+        return HttpRequestClient.Method.{{.method}};
+    }
+
+	@Override
+    public String requestUri() {
+        return {{.uri}};
+    }
+}
diff --git a/tools/goctl/api/ktgen/api.tpl b/tools/goctl/api/ktgen/api.tpl
new file mode 100644
index 00000000..2ec177a6
--- /dev/null
+++ b/tools/goctl/api/ktgen/api.tpl
@@ -0,0 +1,22 @@
+package {{with .Info}}{{.Desc}}{{end}}
+
+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.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 ""}}
+		req:{{.Name}},{{end}}{{end}}
+		onOk: (({{with .ResponseType}}{{.Name}}{{end}}) -> Unit)? = null,
+        onFail: ((String) -> Unit)? = null,
+        eventually: (() -> Unit)? = null
+    ){
+        apiRequest("{{upperCase .Method}}","{{.Path}}",{{with .RequestType}}{{if ne .Name ""}}body=req,{{end}}{{end}} onOk = { {{with .ResponseType}}
+            onOk?.invoke({{if ne .Name ""}}Gson().fromJson(it,{{.Name}}::class.java){{end}}){{end}}
+        }, onFail = onFail, eventually =eventually)
+    }
+	{{end}}{{end}}
+}
diff --git a/tools/goctl/api/ktgen/apibase.tpl b/tools/goctl/api/ktgen/apibase.tpl
new file mode 100644
index 00000000..4aff68a5
--- /dev/null
+++ b/tools/goctl/api/ktgen/apibase.tpl
@@ -0,0 +1,61 @@
+package {{.}}
+
+import com.google.gson.Gson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.io.OutputStreamWriter
+import java.net.HttpURLConnection
+import java.net.URL
+
+const val SERVER = "http://localhost:8080"
+
+suspend fun apiRequest(
+    method: String,
+    uri: String,
+    body: Any = "",
+    onOk: ((String) -> Unit)? = null,
+    onFail: ((String) -> Unit)? = null,
+    eventually: (() -> Unit)? = null
+) = withContext(Dispatchers.IO) {
+    val url = URL(SERVER + uri)
+    with(url.openConnection() as HttpURLConnection) {
+        connectTimeout = 3000
+        requestMethod = method
+        doInput = true
+        if (method == "POST" || method == "PUT" || method == "PATCH") {
+            setRequestProperty("Content-Type", "application/json")
+            doOutput = true
+            val data = when (body) {
+                is String -> {
+                    body
+                }
+                else -> {
+                    Gson().toJson(body)
+                }
+            }
+            val wr = OutputStreamWriter(outputStream)
+            wr.write(data)
+            wr.flush()
+        }
+
+         try {
+            if (responseCode >= 400) {
+                BufferedReader(InputStreamReader(errorStream)).use {
+                    val response = it.readText()
+                    onFail?.invoke(response)
+                }
+                return@with
+            }
+            //response
+            BufferedReader(InputStreamReader(inputStream)).use {
+                val response = it.readText()
+                onOk?.invoke(response)
+            }
+        } catch (e: Exception) {
+            e.message?.let { onFail?.invoke(it) }
+        }
+    }
+    eventually?.invoke()
+}
diff --git a/tools/goctl/api/ktgen/cmd.go b/tools/goctl/api/ktgen/cmd.go
index f0168cf4..65a81e0b 100644
--- a/tools/goctl/api/ktgen/cmd.go
+++ b/tools/goctl/api/ktgen/cmd.go
@@ -7,7 +7,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/api/parser"
 )
 
-// KtCommand the generate kotlin code command entrance
+// KtCommand generates kotlin code command entrance
 func KtCommand(c *cli.Context) error {
 	apiFile := c.String("api")
 	if apiFile == "" {
diff --git a/tools/goctl/api/ktgen/gen.go b/tools/goctl/api/ktgen/gen.go
index 73969cf4..b46f4f16 100644
--- a/tools/goctl/api/ktgen/gen.go
+++ b/tools/goctl/api/ktgen/gen.go
@@ -1,6 +1,7 @@
 package ktgen
 
 import (
+	_ "embed"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -10,91 +11,11 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
 )
 
-const (
-	apiBaseTemplate = `package {{.}}
-
-import com.google.gson.Gson
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import java.io.BufferedReader
-import java.io.InputStreamReader
-import java.io.OutputStreamWriter
-import java.net.HttpURLConnection
-import java.net.URL
-
-const val SERVER = "http://localhost:8080"
-
-suspend fun apiRequest(
-    method: String,
-    uri: String,
-    body: Any = "",
-    onOk: ((String) -> Unit)? = null,
-    onFail: ((String) -> Unit)? = null,
-    eventually: (() -> Unit)? = null
-) = withContext(Dispatchers.IO) {
-    val url = URL(SERVER + uri)
-    with(url.openConnection() as HttpURLConnection) {
-        connectTimeout = 3000
-        requestMethod = method
-        doInput = true
-        if (method == "POST" || method == "PUT" || method == "PATCH") {
-            setRequestProperty("Content-Type", "application/json")
-            doOutput = true
-            val data = when (body) {
-                is String -> {
-                    body
-                }
-                else -> {
-                    Gson().toJson(body)
-                }
-            }
-            val wr = OutputStreamWriter(outputStream)
-            wr.write(data)
-            wr.flush()
-        }
-
-         try {
-            if (responseCode >= 400) {
-                BufferedReader(InputStreamReader(errorStream)).use {
-                    val response = it.readText()
-                    onFail?.invoke(response)
-                }
-                return@with
-            }
-            //response
-            BufferedReader(InputStreamReader(inputStream)).use {
-                val response = it.readText()
-                onOk?.invoke(response)
-            }
-        } catch (e: Exception) {
-            e.message?.let { onFail?.invoke(it) }
-        }
-    }
-    eventually?.invoke()
-}
-`
-	apiTemplate = `package {{with .Info}}{{.Desc}}{{end}}
-
-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.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 ""}}
-		req:{{.Name}},{{end}}{{end}}
-		onOk: (({{with .ResponseType}}{{.Name}}{{end}}) -> Unit)? = null,
-        onFail: ((String) -> Unit)? = null,
-        eventually: (() -> Unit)? = null
-    ){
-        apiRequest("{{upperCase .Method}}","{{.Path}}",{{with .RequestType}}{{if ne .Name ""}}body=req,{{end}}{{end}} onOk = { {{with .ResponseType}}
-            onOk?.invoke({{if ne .Name ""}}Gson().fromJson(it,{{.Name}}::class.java){{end}}){{end}}
-        }, onFail = onFail, eventually =eventually)
-    }
-	{{end}}{{end}}
-}`
+var (
+	//go:embed apibase.tpl
+	apiBaseTemplate string
+	//go:embed api.tpl
+	apiTemplate string
 )
 
 func genBase(dir, pkg string, api *spec.ApiSpec) error {
diff --git a/tools/goctl/api/new/api.tpl b/tools/goctl/api/new/api.tpl
new file mode 100644
index 00000000..9d9898f5
--- /dev/null
+++ b/tools/goctl/api/new/api.tpl
@@ -0,0 +1,12 @@
+type Request {
+  Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
+}
+
+type Response {
+  Message string ` + "`" + `json:"message"` + "`" + `
+}
+
+service {{.name}}-api {
+  @handler {{.handler}}Handler
+  get /from/:name(Request) returns (Response)
+}
diff --git a/tools/goctl/api/new/newservice.go b/tools/goctl/api/new/newservice.go
index 83b6fd20..c215c44e 100644
--- a/tools/goctl/api/new/newservice.go
+++ b/tools/goctl/api/new/newservice.go
@@ -1,11 +1,12 @@
 package new
 
 import (
+	_ "embed"
 	"errors"
+	"html/template"
 	"os"
 	"path/filepath"
 	"strings"
-	"text/template"
 
 	"github.com/urfave/cli"
 	"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
@@ -14,20 +15,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const apiTemplate = `
-type Request {
-  Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` 
-}
-
-type Response {
-  Message string ` + "`" + `json:"message"` + "`" + `
-}
-
-service {{.name}}-api {
-  @handler {{.handler}}Handler
-  get /from/:name(Request) returns (Response)
-}
-`
+//go:embed api.tpl
+var apiTemplate string
 
 // CreateServiceCommand fast create service
 func CreateServiceCommand(c *cli.Context) error {
diff --git a/tools/goctl/api/parser/g4/ast/apiparser.go b/tools/goctl/api/parser/g4/ast/apiparser.go
index b19ba666..214d6956 100644
--- a/tools/goctl/api/parser/g4/ast/apiparser.go
+++ b/tools/goctl/api/parser/g4/ast/apiparser.go
@@ -15,12 +15,18 @@ import (
 type (
 	// Parser provides api parsing capabilities
 	Parser struct {
-		linePrefix string
-		debug      bool
-		log        console.Console
 		antlr.DefaultErrorListener
+		linePrefix               string
+		debug                    bool
+		log                      console.Console
 		src                      string
 		skipCheckTypeDeclaration bool
+		handlerMap               map[string]PlaceHolder
+		routeMap                 map[string]PlaceHolder
+		typeMap                  map[string]PlaceHolder
+		fileMap                  map[string]PlaceHolder
+		importStatck             importStack
+		syntax                   *SyntaxExpr
 	}
 
 	// ParserOption defines an function with argument Parser
@@ -35,6 +41,10 @@ func NewParser(options ...ParserOption) *Parser {
 	for _, opt := range options {
 		opt(p)
 	}
+	p.handlerMap = make(map[string]PlaceHolder)
+	p.routeMap = make(map[string]PlaceHolder)
+	p.typeMap = make(map[string]PlaceHolder)
+	p.fileMap = make(map[string]PlaceHolder)
 
 	return p
 }
@@ -84,6 +94,7 @@ func (p *Parser) Parse(filename string) (*Api, error) {
 		return nil, err
 	}
 
+	p.importStatck.push(p.src)
 	return p.parse(filename, data)
 }
 
@@ -100,6 +111,7 @@ func (p *Parser) ParseContent(content string, filename ...string) (*Api, error)
 		p.src = abs
 	}
 
+	p.importStatck.push(p.src)
 	return p.parse(f, content)
 }
 
@@ -113,12 +125,43 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
 
 	var apiAstList []*Api
 	apiAstList = append(apiAstList, root)
-	for _, imp := range root.Import {
+	p.storeVerificationInfo(root)
+	p.syntax = root.Syntax
+	impApiAstList, err := p.invokeImportedApi(root.Import)
+	if err != nil {
+		return nil, err
+	}
+	apiAstList = append(apiAstList, impApiAstList...)
+
+	if !p.skipCheckTypeDeclaration {
+		err = p.checkTypeDeclaration(apiAstList)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	allApi := p.memberFill(apiAstList)
+	return allApi, nil
+}
+
+func (p *Parser) invokeImportedApi(imports []*ImportExpr) ([]*Api, error) {
+	var apiAstList []*Api
+	for _, imp := range imports {
 		dir := filepath.Dir(p.src)
 		impPath := strings.ReplaceAll(imp.Value.Text(), "\"", "")
 		if !filepath.IsAbs(impPath) {
 			impPath = filepath.Join(dir, impPath)
 		}
+		// import cycle check
+		if err := p.importStatck.push(impPath); err != nil {
+			return nil, err
+		}
+		// ignore already imported file
+		if p.alreadyImported(impPath) {
+			continue
+		}
+		p.fileMap[impPath] = PlaceHolder{}
+
 		data, err := p.readContent(impPath)
 		if err != nil {
 			return nil, err
@@ -129,23 +172,26 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
 			return nil, err
 		}
 
-		err = p.valid(root, nestedApi)
+		err = p.valid(nestedApi)
 		if err != nil {
 			return nil, err
 		}
-
+		p.storeVerificationInfo(nestedApi)
 		apiAstList = append(apiAstList, nestedApi)
-	}
+		list, err := p.invokeImportedApi(nestedApi.Import)
+		p.importStatck.pop()
+		apiAstList = append(apiAstList, list...)
 
-	if !p.skipCheckTypeDeclaration {
-		err = p.checkTypeDeclaration(apiAstList)
 		if err != nil {
 			return nil, err
 		}
 	}
+	return apiAstList, nil
+}
 
-	allApi := p.memberFill(apiAstList)
-	return allApi, nil
+func (p *Parser) alreadyImported(filename string) bool {
+	_, ok := p.fileMap[filename]
+	return ok
 }
 
 func (p *Parser) invoke(linePrefix, content string) (v *Api, err error) {
@@ -184,58 +230,48 @@ func (p *Parser) invoke(linePrefix, content string) (v *Api, err error) {
 	return
 }
 
-func (p *Parser) valid(mainApi, nestedApi *Api) error {
-	err := p.nestedApiCheck(mainApi, nestedApi)
-	if err != nil {
-		return err
-	}
-
-	mainHandlerMap := make(map[string]PlaceHolder)
-	mainRouteMap := make(map[string]PlaceHolder)
-	mainTypeMap := make(map[string]PlaceHolder)
-
-	routeMap := func(list []*ServiceRoute) (map[string]PlaceHolder, map[string]PlaceHolder) {
-		handlerMap := make(map[string]PlaceHolder)
-		routeMap := make(map[string]PlaceHolder)
-
+// storeVerificationInfo stores information for verification
+func (p *Parser) storeVerificationInfo(api *Api) {
+	routeMap := func(list []*ServiceRoute) {
 		for _, g := range list {
 			handler := g.GetHandler()
 			if handler.IsNotNil() {
 				handlerName := handler.Text()
-				handlerMap[handlerName] = Holder
+				p.handlerMap[handlerName] = Holder
 				route := fmt.Sprintf("%s://%s", g.Route.Method.Text(), g.Route.Path.Text())
-				routeMap[route] = Holder
+				p.routeMap[route] = Holder
 			}
 		}
+	}
 
-		return handlerMap, routeMap
+	for _, each := range api.Service {
+		routeMap(each.ServiceApi.ServiceRoute)
 	}
 
-	for _, each := range mainApi.Service {
-		h, r := routeMap(each.ServiceApi.ServiceRoute)
+	for _, each := range api.Type {
+		p.typeMap[each.NameExpr().Text()] = Holder
+	}
+}
 
-		for k, v := range h {
-			mainHandlerMap[k] = v
-		}
+func (p *Parser) valid(nestedApi *Api) error {
 
-		for k, v := range r {
-			mainRouteMap[k] = v
+	if p.syntax != nil && nestedApi.Syntax != nil {
+		if p.syntax.Version.Text() != nestedApi.Syntax.Version.Text() {
+			syntaxToken := nestedApi.Syntax.Syntax
+			return fmt.Errorf("%s line %d:%d multiple syntax declaration, expecting syntax '%s', but found '%s'",
+				nestedApi.LinePrefix, syntaxToken.Line(), syntaxToken.Column(), p.syntax.Version.Text(), nestedApi.Syntax.Version.Text())
 		}
 	}
 
-	for _, each := range mainApi.Type {
-		mainTypeMap[each.NameExpr().Text()] = Holder
-	}
-
 	// duplicate route check
-	err = p.duplicateRouteCheck(nestedApi, mainHandlerMap, mainRouteMap)
+	err := p.duplicateRouteCheck(nestedApi)
 	if err != nil {
 		return err
 	}
 
 	// duplicate type check
 	for _, each := range nestedApi.Type {
-		if _, ok := mainTypeMap[each.NameExpr().Text()]; ok {
+		if _, ok := p.typeMap[each.NameExpr().Text()]; ok {
 			return fmt.Errorf("%s line %d:%d duplicate type declaration '%s'",
 				nestedApi.LinePrefix, each.NameExpr().Line(), each.NameExpr().Column(), each.NameExpr().Text())
 		}
@@ -244,7 +280,7 @@ func (p *Parser) valid(mainApi, nestedApi *Api) error {
 	return nil
 }
 
-func (p *Parser) duplicateRouteCheck(nestedApi *Api, mainHandlerMap, mainRouteMap map[string]PlaceHolder) error {
+func (p *Parser) duplicateRouteCheck(nestedApi *Api) error {
 	for _, each := range nestedApi.Service {
 		var prefix, group string
 		if each.AtServer != nil {
@@ -253,7 +289,7 @@ func (p *Parser) duplicateRouteCheck(nestedApi *Api, mainHandlerMap, mainRouteMa
 				prefix = p.Text()
 			}
 			g := each.AtServer.Kv.Get(groupKey)
-			if p != nil {
+			if g != nil {
 				group = g.Text()
 			}
 		}
@@ -267,13 +303,13 @@ func (p *Parser) duplicateRouteCheck(nestedApi *Api, mainHandlerMap, mainRouteMa
 			if len(group) > 0 {
 				handlerKey = fmt.Sprintf("%s/%s", group, handler.Text())
 			}
-			if _, ok := mainHandlerMap[handlerKey]; ok {
+			if _, ok := p.handlerMap[handlerKey]; ok {
 				return fmt.Errorf("%s line %d:%d duplicate handler '%s'",
 					nestedApi.LinePrefix, handler.Line(), handler.Column(), handlerKey)
 			}
 
-			p := fmt.Sprintf("%s://%s", r.Route.Method.Text(), path.Join(prefix, r.Route.Path.Text()))
-			if _, ok := mainRouteMap[p]; ok {
+			routeKey := fmt.Sprintf("%s://%s", r.Route.Method.Text(), path.Join(prefix, r.Route.Path.Text()))
+			if _, ok := p.routeMap[routeKey]; ok {
 				return fmt.Errorf("%s line %d:%d duplicate route '%s'",
 					nestedApi.LinePrefix, r.Route.Method.Line(), r.Route.Method.Column(), r.Route.Method.Text()+" "+r.Route.Path.Text())
 			}
diff --git a/tools/goctl/api/parser/g4/ast/importstack.go b/tools/goctl/api/parser/g4/ast/importstack.go
new file mode 100644
index 00000000..fe5e339a
--- /dev/null
+++ b/tools/goctl/api/parser/g4/ast/importstack.go
@@ -0,0 +1,23 @@
+package ast
+
+import "errors"
+
+// ErrImportCycleNotAllowed defines an error for circular importing
+var ErrImportCycleNotAllowed = errors.New("import cycle not allowed")
+
+// importStack a stack of import paths
+type importStack []string
+
+func (s *importStack) push(p string) error {
+	for _, x := range *s {
+		if x == p {
+			return ErrImportCycleNotAllowed
+		}
+	}
+	*s = append(*s, p)
+	return nil
+}
+
+func (s *importStack) pop() {
+	*s = (*s)[0 : len(*s)-1]
+}
diff --git a/tools/goctl/api/tsgen/components.tpl b/tools/goctl/api/tsgen/components.tpl
new file mode 100644
index 00000000..5a0cd9dd
--- /dev/null
+++ b/tools/goctl/api/tsgen/components.tpl
@@ -0,0 +1,3 @@
+/ Code generated by goctl. DO NOT EDIT.
+
+{{.componentTypes}}
diff --git a/tools/goctl/api/tsgen/gencomponents.go b/tools/goctl/api/tsgen/gencomponents.go
index ce08280d..aabd704f 100644
--- a/tools/goctl/api/tsgen/gencomponents.go
+++ b/tools/goctl/api/tsgen/gencomponents.go
@@ -1,6 +1,7 @@
 package tsgen
 
 import (
+	_ "embed"
 	"path"
 	"strings"
 	"text/template"
@@ -10,12 +11,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const (
-	componentsTemplate = `// Code generated by goctl. DO NOT EDIT.
-
-{{.componentTypes}}
-`
-)
+//go:embed components.tpl
+var componentsTemplate string
 
 func genComponents(dir string, api *spec.ApiSpec) error {
 	types := api.Types
diff --git a/tools/goctl/api/tsgen/genpacket.go b/tools/goctl/api/tsgen/genpacket.go
index f8d66d05..99296ad4 100644
--- a/tools/goctl/api/tsgen/genpacket.go
+++ b/tools/goctl/api/tsgen/genpacket.go
@@ -1,6 +1,7 @@
 package tsgen
 
 import (
+	_ "embed"
 	"fmt"
 	"path"
 	"strings"
@@ -12,12 +13,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const (
-	handlerTemplate = `{{.imports}}
-
-{{.apis}}
-`
-)
+//go:embed handler.tpl
+var handlerTemplate string
 
 func genHandler(dir, webAPI, caller string, api *spec.ApiSpec, unwrapAPI bool) error {
 	filename := strings.Replace(api.Service.Name, "-api", "", 1) + ".ts"
diff --git a/tools/goctl/api/tsgen/handler.tpl b/tools/goctl/api/tsgen/handler.tpl
new file mode 100644
index 00000000..bd09f79c
--- /dev/null
+++ b/tools/goctl/api/tsgen/handler.tpl
@@ -0,0 +1,3 @@
+{{.imports}}
+
+{{.apis}}
diff --git a/tools/goctl/docker/docker.tpl b/tools/goctl/docker/docker.tpl
new file mode 100644
index 00000000..40ef371d
--- /dev/null
+++ b/tools/goctl/docker/docker.tpl
@@ -0,0 +1,32 @@
+FROM golang:{{.Version}}alpine AS builder
+
+LABEL stage=gobuilder
+
+ENV CGO_ENABLED 0
+{{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
+{{end}}{{if .HasTimezone}}
+RUN apk update --no-cache && apk add --no-cache tzdata
+{{end}}
+WORKDIR /build
+
+ADD go.mod .
+ADD go.sum .
+RUN go mod download
+COPY . .
+{{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc
+{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
+
+
+FROM {{.BaseImage}}
+
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
+{{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}}
+{{if .HasPort}}
+EXPOSE {{.Port}}
+{{end}}
+CMD ["./{{.ExeFile}}"{{.Argument}}]
diff --git a/tools/goctl/docker/template.go b/tools/goctl/docker/template.go
index 8c1b6ab7..c18eab37 100644
--- a/tools/goctl/docker/template.go
+++ b/tools/goctl/docker/template.go
@@ -1,6 +1,8 @@
 package docker
 
 import (
+	_ "embed"
+
 	"github.com/urfave/cli"
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
@@ -8,41 +10,11 @@ import (
 const (
 	category           = "docker"
 	dockerTemplateFile = "docker.tpl"
-	dockerTemplate     = `FROM golang:{{.Version}}alpine AS builder
-
-LABEL stage=gobuilder
-
-ENV CGO_ENABLED 0
-{{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
-{{end}}{{if .HasTimezone}}
-RUN apk update --no-cache && apk add --no-cache tzdata
-{{end}}
-WORKDIR /build
-
-ADD go.mod .
-ADD go.sum .
-RUN go mod download
-COPY . .
-{{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc
-{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
-
-
-FROM {{.BaseImage}}
-
-COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
-{{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}}
-{{if .HasPort}}
-EXPOSE {{.Port}}
-{{end}}
-CMD ["./{{.ExeFile}}"{{.Argument}}]
-`
 )
 
+//go:embed docker.tpl
+var dockerTemplate string
+
 // Clean deletes all templates files
 func Clean() error {
 	return pathx.Clean(category)
diff --git a/tools/goctl/env/install.go b/tools/goctl/env/install.go
new file mode 100644
index 00000000..6edea2f1
--- /dev/null
+++ b/tools/goctl/env/install.go
@@ -0,0 +1,9 @@
+package env
+
+import "github.com/urfave/cli"
+
+func Install(c *cli.Context) error {
+	force := c.Bool("force")
+	verbose := c.Bool("verbose")
+	return Prepare(true, force, verbose)
+}
diff --git a/tools/goctl/go.mod b/tools/goctl/go.mod
index ec1544b6..0cd2c3b6 100644
--- a/tools/goctl/go.mod
+++ b/tools/goctl/go.mod
@@ -13,5 +13,5 @@ require (
 	github.com/urfave/cli v1.22.5
 	github.com/zeromicro/antlr v0.0.1
 	github.com/zeromicro/ddl-parser v1.0.3
-	github.com/zeromicro/go-zero v1.3.1
+	github.com/zeromicro/go-zero v1.3.2
 )
diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum
index 1554883f..29b24f11 100644
--- a/tools/goctl/go.sum
+++ b/tools/goctl/go.sum
@@ -9,17 +9,27 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T
 cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
 cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@@ -159,12 +169,15 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -182,7 +195,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 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=
@@ -190,12 +205,16 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 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/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -230,6 +249,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -263,11 +283,13 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 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=
@@ -311,10 +333,12 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -349,7 +373,9 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
@@ -358,8 +384,8 @@ 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 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=
+github.com/zeromicro/go-zero v1.3.2 h1:2HcmceZDEGwZWvofCG+0GXyh+Gtz/wKCW4Fq8Mb7KIg=
+github.com/zeromicro/go-zero v1.3.2/go.mod h1:DEj3Fwj1Ui1ltsgf6YqwTL9nD4+tYzIRX0c1pWtQo1E=
 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=
@@ -367,6 +393,7 @@ 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=
 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.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y=
 go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
 go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc=
@@ -441,6 +468,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -450,14 +478,20 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/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=
@@ -466,12 +500,16 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -506,8 +544,14 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -569,8 +613,18 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -589,12 +643,20 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -612,9 +674,19 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx
 google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
 google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 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=
@@ -625,6 +697,10 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 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=
@@ -638,6 +714,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 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=
@@ -674,6 +751,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
 k8s.io/apimachinery v0.20.12/go.mod h1:uM7hCI0NyBymUwgshMgZyte475lxhr+QH6h3cvdnzEc=
 k8s.io/client-go v0.20.12/go.mod h1:NBJj6Evp73Xy/4v/O/RDRaH0+3JoxNfjRxkyRgrdbsA=
diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go
index e91d7f8f..69f98487 100644
--- a/tools/goctl/goctl.go
+++ b/tools/goctl/goctl.go
@@ -57,6 +57,21 @@ var commands = []cli.Command{
 			},
 		},
 		Subcommands: []cli.Command{
+			{
+				Name:   "install",
+				Usage:  "goctl env installation",
+				Action: env.Install,
+				Flags: []cli.Flag{
+					cli.BoolFlag{
+						Name:  "force,f",
+						Usage: "silent installation of non-existent dependencies",
+					},
+					cli.BoolFlag{
+						Name:  "verbose, v",
+						Usage: "enable log output",
+					},
+				},
+			},
 			{
 				Name:  "check",
 				Usage: "detect goctl env and dependency tools",
diff --git a/tools/goctl/internal/version/version.go b/tools/goctl/internal/version/version.go
index ff9e96ba..1bbbe76e 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.4"
+const BuildVersion = "1.3.5"
 
 var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
 
diff --git a/tools/goctl/kube/deployment.go b/tools/goctl/kube/deployment.tpl
similarity index 97%
rename from tools/goctl/kube/deployment.go
rename to tools/goctl/kube/deployment.tpl
index 0e493445..7b81d609 100644
--- a/tools/goctl/kube/deployment.go
+++ b/tools/goctl/kube/deployment.tpl
@@ -1,6 +1,4 @@
-package kube
-
-var deploymentTemplate = `apiVersion: apps/v1
+apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: {{.Name}}
@@ -115,4 +113,3 @@ spec:
     resource:
       name: memory
       targetAverageUtilization: 80
-`
diff --git a/tools/goctl/kube/job.go b/tools/goctl/kube/job.tpl
similarity index 94%
rename from tools/goctl/kube/job.go
rename to tools/goctl/kube/job.tpl
index a114fe79..0da72ede 100644
--- a/tools/goctl/kube/job.go
+++ b/tools/goctl/kube/job.tpl
@@ -1,6 +1,4 @@
-package kube
-
-var jobTmeplate = `apiVersion: batch/v1
+apiVersion: batch/v1
 kind: CronJob
 metadata:
   name: {{.Name}}
@@ -37,4 +35,3 @@ spec:
           - name: timezone
             hostPath:
               path: /usr/share/zoneinfo/Asia/Shanghai
-`
diff --git a/tools/goctl/kube/kube.go b/tools/goctl/kube/kube.go
index 79575bff..060ce652 100644
--- a/tools/goctl/kube/kube.go
+++ b/tools/goctl/kube/kube.go
@@ -1,6 +1,7 @@
 package kube
 
 import (
+	_ "embed"
 	"errors"
 	"fmt"
 	"text/template"
@@ -19,6 +20,13 @@ const (
 	portLimit          = 32767
 )
 
+var (
+	//go:embed deployment.tpl
+	deploymentTemplate string
+	//go:embed job.tpl
+	jobTemplate string
+)
+
 // Deployment describes the k8s deployment yaml
 type Deployment struct {
 	Name           string
@@ -113,7 +121,7 @@ func Clean() error {
 func GenTemplates(_ *cli.Context) error {
 	return pathx.InitTemplates(category, map[string]string{
 		deployTemplateFile: deploymentTemplate,
-		jobTemplateFile:    jobTmeplate,
+		jobTemplateFile:    jobTemplate,
 	})
 }
 
@@ -131,6 +139,6 @@ func Update() error {
 
 	return pathx.InitTemplates(category, map[string]string{
 		deployTemplateFile: deploymentTemplate,
-		jobTemplateFile:    jobTmeplate,
+		jobTemplateFile:    jobTemplate,
 	})
 }
diff --git a/tools/goctl/model/mongo/template/error.tpl b/tools/goctl/model/mongo/template/error.tpl
new file mode 100644
index 00000000..cb8be412
--- /dev/null
+++ b/tools/goctl/model/mongo/template/error.tpl
@@ -0,0 +1,6 @@
+package model
+
+import "errors"
+
+var ErrNotFound = errors.New("not found")
+var ErrInvalidObjectId = errors.New("invalid objectId")
diff --git a/tools/goctl/model/mongo/template/model.tpl b/tools/goctl/model/mongo/template/model.tpl
new file mode 100644
index 00000000..bf807d01
--- /dev/null
+++ b/tools/goctl/model/mongo/template/model.tpl
@@ -0,0 +1,98 @@
+package model
+
+import (
+    "context"
+
+    "github.com/globalsign/mgo/bson"
+     {{if .Cache}}cachec "github.com/zeromicro/go-zero/core/stores/cache"
+	"github.com/zeromicro/go-zero/core/stores/mongoc"{{else}}"github.com/zeromicro/go-zero/core/stores/mongo"{{end}}
+)
+
+{{if .Cache}}var prefix{{.Type}}CacheKey = "cache:{{.Type}}:"{{end}}
+
+type {{.Type}}Model interface{
+	Insert(ctx context.Context,data *{{.Type}}) error
+	FindOne(ctx context.Context,id string) (*{{.Type}}, error)
+	Update(ctx context.Context,data *{{.Type}}) error
+	Delete(ctx context.Context,id string) error
+}
+
+type default{{.Type}}Model struct {
+    {{if .Cache}}*mongoc.Model{{else}}*mongo.Model{{end}}
+}
+
+func New{{.Type}}Model(url, collection string{{if .Cache}}, c cachec.CacheConf{{end}}) {{.Type}}Model {
+	return &default{{.Type}}Model{
+		Model: {{if .Cache}}mongoc.MustNewModel(url, collection, c){{else}}mongo.MustNewModel(url, collection){{end}},
+	}
+}
+
+
+func (m *default{{.Type}}Model) Insert(ctx context.Context, data *{{.Type}}) error {
+    if !data.ID.Valid() {
+        data.ID = bson.NewObjectId()
+    }
+
+    session, err := m.TakeSession()
+    if err != nil {
+        return err
+    }
+
+    defer m.PutSession(session)
+    return m.GetCollection(session).Insert(data)
+}
+
+func (m *default{{.Type}}Model) FindOne(ctx context.Context, id string) (*{{.Type}}, error) {
+    if !bson.IsObjectIdHex(id) {
+        return nil, ErrInvalidObjectId
+    }
+
+    session, err := m.TakeSession()
+    if err != nil {
+        return nil, err
+    }
+
+    defer m.PutSession(session)
+    var data {{.Type}}
+    {{if .Cache}}key := prefix{{.Type}}CacheKey + id
+    err = m.GetCollection(session).FindOneId(&data, key, bson.ObjectIdHex(id))
+	{{- else}}
+	err = m.GetCollection(session).FindId(bson.ObjectIdHex(id)).One(&data)
+	{{- end}}
+    switch err {
+    case nil:
+        return &data,nil
+    case {{if .Cache}}mongoc.ErrNotFound{{else}}mongo.ErrNotFound{{end}}:
+        return nil,ErrNotFound
+    default:
+        return nil,err
+    }
+}
+
+func (m *default{{.Type}}Model) Update(ctx context.Context, data *{{.Type}}) error {
+    session, err := m.TakeSession()
+    if err != nil {
+        return err
+    }
+
+    defer m.PutSession(session)
+	{{if .Cache}}key := prefix{{.Type}}CacheKey + data.ID.Hex()
+    return m.GetCollection(session).UpdateId(data.ID, data, key)
+	{{- else}}
+	return m.GetCollection(session).UpdateId(data.ID, data)
+	{{- end}}
+}
+
+func (m *default{{.Type}}Model) Delete(ctx context.Context, id string) error {
+    session, err := m.TakeSession()
+    if err != nil {
+        return err
+    }
+
+    defer m.PutSession(session)
+    {{if .Cache}}key := prefix{{.Type}}CacheKey + id
+    return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id), key)
+	{{- else}}
+	return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id))
+	{{- end}}
+}
diff --git a/tools/goctl/model/mongo/template/template.go b/tools/goctl/model/mongo/template/template.go
index 5ab4865a..9477f5f2 100644
--- a/tools/goctl/model/mongo/template/template.go
+++ b/tools/goctl/model/mongo/template/template.go
@@ -1,112 +1,11 @@
 package template
 
-// Text provides the default template for model to generate
-var Text = `package model
+import _ "embed"
 
-import (
-    "context"
-
-    "github.com/globalsign/mgo/bson"
-     {{if .Cache}}cachec "github.com/zeromicro/go-zero/core/stores/cache"
-	"github.com/zeromicro/go-zero/core/stores/mongoc"{{else}}"github.com/zeromicro/go-zero/core/stores/mongo"{{end}}
-)
-
-{{if .Cache}}var prefix{{.Type}}CacheKey = "cache:{{.Type}}:"{{end}}
-
-type {{.Type}}Model interface{
-	Insert(ctx context.Context,data *{{.Type}}) error
-	FindOne(ctx context.Context,id string) (*{{.Type}}, error)
-	Update(ctx context.Context,data *{{.Type}}) error
-	Delete(ctx context.Context,id string) error
-}
-
-type default{{.Type}}Model struct {
-    {{if .Cache}}*mongoc.Model{{else}}*mongo.Model{{end}}
-}
-
-func New{{.Type}}Model(url, collection string{{if .Cache}}, c cachec.CacheConf{{end}}) {{.Type}}Model {
-	return &default{{.Type}}Model{
-		Model: {{if .Cache}}mongoc.MustNewModel(url, collection, c){{else}}mongo.MustNewModel(url, collection){{end}},
-	}
-}
-
-
-func (m *default{{.Type}}Model) Insert(ctx context.Context, data *{{.Type}}) error {
-    if !data.ID.Valid() {
-        data.ID = bson.NewObjectId()
-    }
-
-    session, err := m.TakeSession()
-    if err != nil {
-        return err
-    }
-
-    defer m.PutSession(session)
-    return m.GetCollection(session).Insert(data)
-}
-
-func (m *default{{.Type}}Model) FindOne(ctx context.Context, id string) (*{{.Type}}, error) {
-    if !bson.IsObjectIdHex(id) {
-        return nil, ErrInvalidObjectId
-    }
-
-    session, err := m.TakeSession()
-    if err != nil {
-        return nil, err
-    }
-
-    defer m.PutSession(session)
-    var data {{.Type}}
-    {{if .Cache}}key := prefix{{.Type}}CacheKey + id
-    err = m.GetCollection(session).FindOneId(&data, key, bson.ObjectIdHex(id))
-	{{- else}}
-	err = m.GetCollection(session).FindId(bson.ObjectIdHex(id)).One(&data)
-	{{- end}}
-    switch err {
-    case nil:
-        return &data,nil
-    case {{if .Cache}}mongoc.ErrNotFound{{else}}mongo.ErrNotFound{{end}}:
-        return nil,ErrNotFound
-    default:
-        return nil,err
-    }
-}
-
-func (m *default{{.Type}}Model) Update(ctx context.Context, data *{{.Type}}) error {
-    session, err := m.TakeSession()
-    if err != nil {
-        return err
-    }
-
-    defer m.PutSession(session)
-	{{if .Cache}}key := prefix{{.Type}}CacheKey + data.ID.Hex()
-    return m.GetCollection(session).UpdateId(data.ID, data, key)
-	{{- else}}
-	return m.GetCollection(session).UpdateId(data.ID, data)
-	{{- end}}
-}
-
-func (m *default{{.Type}}Model) Delete(ctx context.Context, id string) error {
-    session, err := m.TakeSession()
-    if err != nil {
-        return err
-    }
-
-    defer m.PutSession(session)
-    {{if .Cache}}key := prefix{{.Type}}CacheKey + id
-    return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id), key)
-	{{- else}}
-	return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id))
-	{{- end}}
-}
-`
+// Text provides the default template for model to generate.
+//go:embed model.tpl
+var Text string
 
 // Error provides the default template for error definition in mongo code generation.
-var Error = `
-package model
-
-import "errors"
-
-var ErrNotFound = errors.New("not found")
-var ErrInvalidObjectId = errors.New("invalid objectId")
-`
+//go:embed error.tpl
+var Error string
diff --git a/tools/goctl/model/sql/example/sql/user.sql b/tools/goctl/model/sql/example/sql/user.sql
index 0e705744..48531dc3 100644
--- a/tools/goctl/model/sql/example/sql/user.sql
+++ b/tools/goctl/model/sql/example/sql/user.sql
@@ -21,7 +21,7 @@ CREATE TABLE `user`
 
 CREATE TABLE `student`
 (
-    `id`    bigint                           NOT NULL,
+    `type`    bigint                           NOT NULL,
     `class` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
     `name`  varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
     `age`   tinyint                                   DEFAULT NULL,
@@ -29,6 +29,6 @@ CREATE TABLE `student`
 ) DEFAULT NULL,
   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   `update_time` timestamp NULL DEFAULT NULL,
-  PRIMARY KEY (`id`) USING BTREE,
+  PRIMARY KEY (`type`) USING BTREE,
   UNIQUE KEY `class_name_index` (`class`,`name`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
\ No newline at end of file
diff --git a/tools/goctl/model/sql/gen/delete.go b/tools/goctl/model/sql/gen/delete.go
index 559741ea..0b000518 100644
--- a/tools/goctl/model/sql/gen/delete.go
+++ b/tools/goctl/model/sql/gen/delete.go
@@ -1,6 +1,7 @@
 package gen
 
 import (
+	"sort"
 	"strings"
 
 	"github.com/zeromicro/go-zero/core/collection"
@@ -19,6 +20,10 @@ func genDelete(table Table, withCache, postgreSql bool) (string, string, error)
 		keySet.AddStr(key.DataKeyExpression)
 		keyVariableSet.AddStr(key.KeyLeft)
 	}
+	keys := keySet.KeysStr()
+	sort.Strings(keys)
+	keyVars := keyVariableSet.KeysStr()
+	sort.Strings(keyVars)
 
 	camel := table.Name.ToCamel()
 	text, err := pathx.LoadTemplate(category, deleteTemplateFile, template.Delete)
@@ -32,11 +37,11 @@ func genDelete(table Table, withCache, postgreSql bool) (string, string, error)
 			"upperStartCamelObject":     camel,
 			"withCache":                 withCache,
 			"containsIndexCache":        table.ContainsUniqueCacheKey,
-			"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle(),
+			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
 			"dataType":                  table.PrimaryKey.DataType,
-			"keys":                      strings.Join(keySet.KeysStr(), "\n"),
+			"keys":                      strings.Join(keys, "\n"),
 			"originalPrimaryKey":        wrapWithRawString(table.PrimaryKey.Name.Source(), postgreSql),
-			"keyValues":                 strings.Join(keyVariableSet.KeysStr(), ", "),
+			"keyValues":                 strings.Join(keyVars, ", "),
 			"postgreSql":                postgreSql,
 			"data":                      table,
 		})
@@ -53,7 +58,7 @@ func genDelete(table Table, withCache, postgreSql bool) (string, string, error)
 	deleteMethodOut, err := util.With("deleteMethod").
 		Parse(text).
 		Execute(map[string]interface{}{
-			"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle(),
+			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
 			"dataType":                  table.PrimaryKey.DataType,
 			"data":                      table,
 		})
diff --git a/tools/goctl/model/sql/gen/findone.go b/tools/goctl/model/sql/gen/findone.go
index 814a8f7f..a2063e85 100644
--- a/tools/goctl/model/sql/gen/findone.go
+++ b/tools/goctl/model/sql/gen/findone.go
@@ -21,7 +21,7 @@ func genFindOne(table Table, withCache, postgreSql bool) (string, string, error)
 			"upperStartCamelObject":     camel,
 			"lowerStartCamelObject":     stringx.From(camel).Untitle(),
 			"originalPrimaryKey":        wrapWithRawString(table.PrimaryKey.Name.Source(), postgreSql),
-			"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle(),
+			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
 			"dataType":                  table.PrimaryKey.DataType,
 			"cacheKey":                  table.PrimaryCacheKey.KeyExpression,
 			"cacheKeyVariable":          table.PrimaryCacheKey.KeyLeft,
@@ -41,7 +41,7 @@ func genFindOne(table Table, withCache, postgreSql bool) (string, string, error)
 		Parse(text).
 		Execute(map[string]interface{}{
 			"upperStartCamelObject":     camel,
-			"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle(),
+			"lowerStartCamelPrimaryKey": util.EscapeGolangKeyword(stringx.From(table.PrimaryKey.Name.ToCamel()).Untitle()),
 			"dataType":                  table.PrimaryKey.DataType,
 			"data":                      table,
 		})
diff --git a/tools/goctl/model/sql/gen/findonebyfield.go b/tools/goctl/model/sql/gen/findonebyfield.go
index 1d21d741..8c4f8aef 100644
--- a/tools/goctl/model/sql/gen/findonebyfield.go
+++ b/tools/goctl/model/sql/gen/findonebyfield.go
@@ -59,7 +59,7 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
 	for _, key := range table.UniqueCacheKey {
 		var inJoin, paramJoin Join
 		for _, f := range key.Fields {
-			param := stringx.From(f.Name.ToCamel()).Untitle()
+			param := util.EscapeGolangKeyword(stringx.From(f.Name.ToCamel()).Untitle())
 			inJoin = append(inJoin, fmt.Sprintf("%s %s", param, f.DataType))
 			paramJoin = append(paramJoin, param)
 		}
@@ -82,7 +82,8 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
 	}
 
 	if withCache {
-		text, err := pathx.LoadTemplate(category, findOneByFieldExtraMethodTemplateFile, template.FindOneByFieldExtraMethod)
+		text, err := pathx.LoadTemplate(category, findOneByFieldExtraMethodTemplateFile,
+			template.FindOneByFieldExtraMethod)
 		if err != nil {
 			return nil, err
 		}
@@ -115,7 +116,7 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
 func convertJoin(key Key, postgreSql bool) (in, paramJoinString, originalFieldString string) {
 	var inJoin, paramJoin, argJoin Join
 	for index, f := range key.Fields {
-		param := stringx.From(f.Name.ToCamel()).Untitle()
+		param := util.EscapeGolangKeyword(stringx.From(f.Name.ToCamel()).Untitle())
 		inJoin = append(inJoin, fmt.Sprintf("%s %s", param, f.DataType))
 		paramJoin = append(paramJoin, param)
 		if postgreSql {
diff --git a/tools/goctl/model/sql/gen/insert.go b/tools/goctl/model/sql/gen/insert.go
index 1ca7b312..36cfe8e1 100644
--- a/tools/goctl/model/sql/gen/insert.go
+++ b/tools/goctl/model/sql/gen/insert.go
@@ -2,6 +2,7 @@ package gen
 
 import (
 	"fmt"
+	"sort"
 	"strings"
 
 	"github.com/zeromicro/go-zero/core/collection"
@@ -20,6 +21,10 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
 		keySet.AddStr(key.DataKeyExpression)
 		keyVariableSet.AddStr(key.KeyLeft)
 	}
+	keys := keySet.KeysStr()
+	sort.Strings(keys)
+	keyVars := keyVariableSet.KeysStr()
+	sort.Strings(keyVars)
 
 	expressions := make([]string, 0)
 	expressionValues := make([]string, 0)
@@ -59,8 +64,8 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
 			"lowerStartCamelObject": stringx.From(camel).Untitle(),
 			"expression":            strings.Join(expressions, ", "),
 			"expressionValues":      strings.Join(expressionValues, ", "),
-			"keys":                  strings.Join(keySet.KeysStr(), "\n"),
-			"keyValues":             strings.Join(keyVariableSet.KeysStr(), ", "),
+			"keys":                  strings.Join(keys, "\n"),
+			"keyValues":             strings.Join(keyVars, ", "),
 			"data":                  table,
 		})
 	if err != nil {
diff --git a/tools/goctl/model/sql/gen/keys.go b/tools/goctl/model/sql/gen/keys.go
index 17c71e16..2a19ff47 100644
--- a/tools/goctl/model/sql/gen/keys.go
+++ b/tools/goctl/model/sql/gen/keys.go
@@ -53,8 +53,8 @@ func genCacheKeys(table parser.Table) (Key, []Key) {
 
 func genCacheKey(db, table stringx.String, in []*parser.Field) Key {
 	var (
-		varLeftJoin, varRightJon, fieldNameJoin Join
-		varLeft, varRight, varExpression        string
+		varLeftJoin, varRightJoin, fieldNameJoin Join
+		varLeft, varRight, varExpression         string
 
 		keyLeftJoin, keyRightJoin, keyRightArgJoin, dataRightJoin         Join
 		keyLeft, keyRight, dataKeyRight, keyExpression, dataKeyExpression string
@@ -63,19 +63,19 @@ func genCacheKey(db, table stringx.String, in []*parser.Field) Key {
 	dbName, tableName := util.SafeString(db.Source()), util.SafeString(table.Source())
 	if len(dbName) > 0 {
 		varLeftJoin = append(varLeftJoin, "cache", dbName, tableName)
-		varRightJon = append(varRightJon, "cache", dbName, tableName)
+		varRightJoin = append(varRightJoin, "cache", dbName, tableName)
 		keyLeftJoin = append(keyLeftJoin, dbName, tableName)
 	} else {
 		varLeftJoin = append(varLeftJoin, "cache", tableName)
-		varRightJon = append(varRightJon, "cache", tableName)
+		varRightJoin = append(varRightJoin, "cache", tableName)
 		keyLeftJoin = append(keyLeftJoin, tableName)
 	}
 
 	for _, each := range in {
 		varLeftJoin = append(varLeftJoin, each.Name.Source())
-		varRightJon = append(varRightJon, each.Name.Source())
+		varRightJoin = append(varRightJoin, each.Name.Source())
 		keyLeftJoin = append(keyLeftJoin, each.Name.Source())
-		keyRightJoin = append(keyRightJoin, stringx.From(each.Name.ToCamel()).Untitle())
+		keyRightJoin = append(keyRightJoin, util.EscapeGolangKeyword(stringx.From(each.Name.ToCamel()).Untitle()))
 		keyRightArgJoin = append(keyRightArgJoin, "%v")
 		dataRightJoin = append(dataRightJoin, "data."+each.Name.ToCamel())
 		fieldNameJoin = append(fieldNameJoin, each.Name.Source())
@@ -84,7 +84,7 @@ func genCacheKey(db, table stringx.String, in []*parser.Field) Key {
 	keyLeftJoin = append(keyLeftJoin, "key")
 
 	varLeft = util.SafeString(varLeftJoin.Camel().With("").Untitle())
-	varRight = fmt.Sprintf(`"%s"`, varRightJon.Camel().Untitle().With(":").Source()+":")
+	varRight = fmt.Sprintf(`"%s"`, varRightJoin.Camel().Untitle().With(":").Source()+":")
 	varExpression = fmt.Sprintf(`%s = %s`, varLeft, varRight)
 
 	keyLeft = util.SafeString(keyLeftJoin.Camel().With("").Untitle())
diff --git a/tools/goctl/model/sql/gen/update.go b/tools/goctl/model/sql/gen/update.go
index 5dab0706..57ce3625 100644
--- a/tools/goctl/model/sql/gen/update.go
+++ b/tools/goctl/model/sql/gen/update.go
@@ -1,6 +1,7 @@
 package gen
 
 import (
+	"sort"
 	"strings"
 
 	"github.com/zeromicro/go-zero/core/collection"
@@ -33,6 +34,10 @@ func genUpdate(table Table, withCache, postgreSql bool) (string, string, error)
 		keySet.AddStr(key.DataKeyExpression)
 		keyVariableSet.AddStr(key.KeyLeft)
 	}
+	keys := keySet.KeysStr()
+	sort.Strings(keys)
+	keyVars := keyVariableSet.KeysStr()
+	sort.Strings(keyVars)
 
 	if postgreSql {
 		expressionValues = append([]string{"data." + table.PrimaryKey.Name.ToCamel()}, expressionValues...)
@@ -50,8 +55,8 @@ func genUpdate(table Table, withCache, postgreSql bool) (string, string, error)
 		Execute(map[string]interface{}{
 			"withCache":             withCache,
 			"upperStartCamelObject": camelTableName,
-			"keys":                  strings.Join(keySet.KeysStr(), "\n"),
-			"keyValues":             strings.Join(keyVariableSet.KeysStr(), ", "),
+			"keys":                  strings.Join(keys, "\n"),
+			"keyValues":             strings.Join(keyVars, ", "),
 			"primaryCacheKey":       table.PrimaryCacheKey.DataKeyExpression,
 			"primaryKeyVariable":    table.PrimaryCacheKey.KeyLeft,
 			"lowerStartCamelObject": stringx.From(camelTableName).Untitle(),
diff --git a/tools/goctl/model/sql/model/infoschemamodel.go b/tools/goctl/model/sql/model/infoschemamodel.go
index 6480951a..8a204939 100644
--- a/tools/goctl/model/sql/model/infoschemamodel.go
+++ b/tools/goctl/model/sql/model/infoschemamodel.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/zeromicro/go-zero/core/stores/sqlx"
 	"github.com/zeromicro/go-zero/tools/goctl/model/sql/util"
-	su "github.com/zeromicro/go-zero/tools/goctl/util"
 )
 
 const indexPri = "PRIMARY"
@@ -145,15 +144,14 @@ func (m *InformationSchemaModel) FindIndex(db, table, column string) ([]*DbIndex
 // Convert converts column data into Table
 func (c *ColumnData) Convert() (*Table, error) {
 	var table Table
-	table.Table = su.EscapeGolangKeyword(c.Table)
-	table.Db = su.EscapeGolangKeyword(c.Db)
+	table.Table = c.Table
+	table.Db = c.Db
 	table.Columns = c.Columns
 	table.UniqueIndex = map[string][]*Column{}
 	table.NormalIndex = map[string][]*Column{}
 
 	m := make(map[string][]*Column)
 	for _, each := range c.Columns {
-		each.Name = su.EscapeGolangKeyword(each.Name)
 		each.Comment = util.TrimNewLine(each.Comment)
 		if each.Index != nil {
 			m[each.Index.IndexName] = append(m[each.Index.IndexName], each)
diff --git a/tools/goctl/model/sql/parser/parser.go b/tools/goctl/model/sql/parser/parser.go
index ab992083..37db2690 100644
--- a/tools/goctl/model/sql/parser/parser.go
+++ b/tools/goctl/model/sql/parser/parser.go
@@ -11,7 +11,6 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/model/sql/converter"
 	"github.com/zeromicro/go-zero/tools/goctl/model/sql/model"
 	"github.com/zeromicro/go-zero/tools/goctl/model/sql/util"
-	su "github.com/zeromicro/go-zero/tools/goctl/util"
 	"github.com/zeromicro/go-zero/tools/goctl/util/console"
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
@@ -64,14 +63,13 @@ func parseNameOriginal(ts []*parser.Table) (nameOriginals [][]string) {
 // Parse parses ddl into golang structure
 func Parse(filename, database string) ([]*Table, error) {
 	p := parser.NewParser()
-	ts, err := p.From(filename)
+	tables, err := p.From(filename)
 	if err != nil {
 		return nil, err
 	}
 
-	nameOriginals := parseNameOriginal(ts)
+	nameOriginals := parseNameOriginal(tables)
 
-	tables := GetSafeTables(ts)
 	indexNameGen := func(column ...string) string {
 		return strings.Join(column, "_")
 	}
@@ -371,35 +369,3 @@ func getTableFields(table *model.Table) (map[string]*Field, error) {
 	}
 	return fieldM, nil
 }
-
-// GetSafeTables escapes the golang keywords from sql tables.
-func GetSafeTables(tables []*parser.Table) []*parser.Table {
-	var list []*parser.Table
-	for _, t := range tables {
-		table := GetSafeTable(t)
-		list = append(list, table)
-	}
-
-	return list
-}
-
-// GetSafeTable escapes the golang keywords from sql table.
-func GetSafeTable(table *parser.Table) *parser.Table {
-	table.Name = su.EscapeGolangKeyword(table.Name)
-	for _, c := range table.Columns {
-		c.Name = su.EscapeGolangKeyword(c.Name)
-	}
-
-	for _, e := range table.Constraints {
-		var uniqueKeys, primaryKeys []string
-		for _, u := range e.ColumnUniqueKey {
-			uniqueKeys = append(uniqueKeys, su.EscapeGolangKeyword(u))
-		}
-		for _, p := range e.ColumnPrimaryKey {
-			primaryKeys = append(primaryKeys, su.EscapeGolangKeyword(p))
-		}
-		e.ColumnUniqueKey = uniqueKeys
-		e.ColumnPrimaryKey = primaryKeys
-	}
-	return table
-}
diff --git a/tools/goctl/rpc/cli/cli.go b/tools/goctl/rpc/cli/cli.go
index be98b315..b45f202a 100644
--- a/tools/goctl/rpc/cli/cli.go
+++ b/tools/goctl/rpc/cli/cli.go
@@ -59,6 +59,10 @@ func RPCNew(c *cli.Context) error {
 
 // RPCTemplate is the entry for generate rpc template
 func RPCTemplate(c *cli.Context) error {
+	if c.NumFlags() == 0 {
+		cli.ShowCommandHelpAndExit(c, "template", 1)
+	}
+
 	protoFile := c.String("o")
 	home := c.String("home")
 	remote := c.String("remote")
diff --git a/tools/goctl/rpc/generator/call.tpl b/tools/goctl/rpc/generator/call.tpl
new file mode 100644
index 00000000..27b48796
--- /dev/null
+++ b/tools/goctl/rpc/generator/call.tpl
@@ -0,0 +1,33 @@
+{{.head}}
+
+package {{.filePackage}}
+
+import (
+	"context"
+
+	{{.pbPackage}}
+	{{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}}
+
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+)
+
+type (
+	{{.alias}}
+
+	{{.serviceName}} interface {
+		{{.interface}}
+	}
+
+	default{{.serviceName}} struct {
+		cli zrpc.Client
+	}
+)
+
+func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} {
+	return &default{{.serviceName}}{
+		cli: cli,
+	}
+}
+
+{{.functions}}
diff --git a/tools/goctl/rpc/generator/config.tpl b/tools/goctl/rpc/generator/config.tpl
new file mode 100644
index 00000000..c1f85b99
--- /dev/null
+++ b/tools/goctl/rpc/generator/config.tpl
@@ -0,0 +1,7 @@
+package config
+
+import "github.com/zeromicro/go-zero/zrpc"
+
+type Config struct {
+	zrpc.RpcServerConf
+}
diff --git a/tools/goctl/rpc/generator/etc.tpl b/tools/goctl/rpc/generator/etc.tpl
new file mode 100644
index 00000000..cea664b6
--- /dev/null
+++ b/tools/goctl/rpc/generator/etc.tpl
@@ -0,0 +1,6 @@
+Name: {{.serviceName}}.rpc
+ListenOn: 127.0.0.1:8080
+Etcd:
+  Hosts:
+  - 127.0.0.1:2379
+  Key: {{.serviceName}}.rpc
diff --git a/tools/goctl/rpc/generator/gencall.go b/tools/goctl/rpc/generator/gencall.go
index f8258a3f..86706761 100644
--- a/tools/goctl/rpc/generator/gencall.go
+++ b/tools/goctl/rpc/generator/gencall.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 	"sort"
@@ -17,41 +18,6 @@ import (
 )
 
 const (
-	callTemplateText = `{{.head}}
-
-package {{.filePackage}}
-
-import (
-	"context"
-
-	{{.pbPackage}}
-	{{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}}
-
-	"github.com/zeromicro/go-zero/zrpc"
-	"google.golang.org/grpc"
-)
-
-type (
-	{{.alias}}
-
-	{{.serviceName}} interface {
-		{{.interface}}
-	}
-
-	default{{.serviceName}} struct {
-		cli zrpc.Client
-	}
-)
-
-func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} {
-	return &default{{.serviceName}}{
-		cli: cli,
-	}
-}
-
-{{.functions}}
-`
-
 	callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
 {{end}}{{.method}}(ctx context.Context{{if .hasReq}}, in *{{.pbRequest}}{{end}}, opts ...grpc.CallOption) ({{if .notStream}}*{{.pbResponse}}, {{else}}{{.streamBody}},{{end}} error)`
 
@@ -64,6 +30,9 @@ func (m *default{{.serviceName}}) {{.method}}(ctx context.Context{{if .hasReq}},
 `
 )
 
+//go:embed call.tpl
+var callTemplateText string
+
 // GenCall generates the rpc client code, which is the entry point for the rpc service call.
 // It is a layer of encapsulation for the rpc client and shields the details in the pb.
 func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
diff --git a/tools/goctl/rpc/generator/genconfig.go b/tools/goctl/rpc/generator/genconfig.go
index 46567a5a..a81a07a8 100644
--- a/tools/goctl/rpc/generator/genconfig.go
+++ b/tools/goctl/rpc/generator/genconfig.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -11,14 +12,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const configTemplate = `package config
-
-import "github.com/zeromicro/go-zero/zrpc"
-
-type Config struct {
-	zrpc.RpcServerConf
-}
-`
+//go:embed config.tpl
+var configTemplate string
 
 // GenConfig generates the configuration structure definition file of the rpc service,
 // which contains the zrpc.RpcServerConf configuration item by default.
diff --git a/tools/goctl/rpc/generator/genetc.go b/tools/goctl/rpc/generator/genetc.go
index 4b9fb6fe..a5d92e0c 100644
--- a/tools/goctl/rpc/generator/genetc.go
+++ b/tools/goctl/rpc/generator/genetc.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -13,13 +14,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
 
-const etcTemplate = `Name: {{.serviceName}}.rpc
-ListenOn: 127.0.0.1:8080
-Etcd:
-  Hosts:
-  - 127.0.0.1:2379
-  Key: {{.serviceName}}.rpc
-`
+//go:embed etc.tpl
+var etcTemplate string
 
 // GenEtc generates the yaml configuration file of the rpc service,
 // including host, port monitoring configuration items and etcd configuration
diff --git a/tools/goctl/rpc/generator/genlogic.go b/tools/goctl/rpc/generator/genlogic.go
index 968c3720..9cbc9670 100644
--- a/tools/goctl/rpc/generator/genlogic.go
+++ b/tools/goctl/rpc/generator/genlogic.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -14,40 +15,16 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
 
-const (
-	logicTemplate = `package logic
-
-import (
-	"context"
-
-	{{.imports}}
-
-	"github.com/zeromicro/go-zero/core/logx"
-)
-
-type {{.logicName}} struct {
-	ctx    context.Context
-	svcCtx *svc.ServiceContext
-	logx.Logger
-}
-
-func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} {
-	return &{{.logicName}}{
-		ctx:    ctx,
-		svcCtx: svcCtx,
-		Logger: logx.WithContext(ctx),
-	}
-}
-{{.functions}}
-`
-	logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}}
+const logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}}
 func (l *{{.logicName}}) {{.method}} ({{if .hasReq}}in {{.request}}{{if .stream}},stream {{.streamBody}}{{end}}{{else}}stream {{.streamBody}}{{end}}) ({{if .hasReply}}{{.response}},{{end}} error) {
 	// todo: add your logic here and delete this line
 	
 	return {{if .hasReply}}&{{.responseType}}{},{{end}} nil
 }
 `
-)
+
+//go:embed logic.tpl
+var logicTemplate string
 
 // GenLogic generates the logic file of the rpc service, which corresponds to the RPC definition items in proto.
 func (g *Generator) GenLogic(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
diff --git a/tools/goctl/rpc/generator/genmain.go b/tools/goctl/rpc/generator/genmain.go
index 91bfae85..c5ca2aff 100644
--- a/tools/goctl/rpc/generator/genmain.go
+++ b/tools/goctl/rpc/generator/genmain.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -13,44 +14,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
 
-const mainTemplate = `package main
-
-import (
-	"flag"
-	"fmt"
-
-	{{.imports}}
-
-	"github.com/zeromicro/go-zero/core/conf"
-	"github.com/zeromicro/go-zero/core/service"
-	"github.com/zeromicro/go-zero/zrpc"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/reflection"
-)
-
-var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
-
-func main() {
-	flag.Parse()
-
-	var c config.Config
-	conf.MustLoad(*configFile, &c)
-	ctx := svc.NewServiceContext(c)
-	svr := server.New{{.serviceNew}}Server(ctx)
-
-	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
-		{{.pkg}}.Register{{.service}}Server(grpcServer, svr)
-
-		if c.Mode == service.DevMode || c.Mode == service.TestMode {
-			reflection.Register(grpcServer)
-		}
-	})
-	defer s.Stop()
-
-	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
-	s.Start()
-}
-`
+//go:embed main.tpl
+var mainTemplate string
 
 // GenMain generates the main file of the rpc service, which is an rpc service program call entry
 func (g *Generator) GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
diff --git a/tools/goctl/rpc/generator/genserver.go b/tools/goctl/rpc/generator/genserver.go
index 587a04e6..2a327ec5 100644
--- a/tools/goctl/rpc/generator/genserver.go
+++ b/tools/goctl/rpc/generator/genserver.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 	"strings"
@@ -14,38 +15,16 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
 
-const (
-	serverTemplate = `{{.head}}
-
-package server
-
-import (
-	{{if .notStream}}"context"{{end}}
-
-	{{.imports}}
-)
-
-type {{.server}}Server struct {
-	svcCtx *svc.ServiceContext
-	{{.unimplementedServer}}
-}
-
-func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server {
-	return &{{.server}}Server{
-		svcCtx: svcCtx,
-	}
-}
-
-{{.funcs}}
-`
-	functionTemplate = `
+const functionTemplate = `
 {{if .hasComment}}{{.comment}}{{end}}
 func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{if .hasReq}} in {{.request}}{{end}}{{else}}{{if .hasReq}} in {{.request}},{{end}}stream {{.streamBody}}{{end}}) ({{if .notStream}}{{.response}},{{end}}error) {
 	l := logic.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx)
 	return l.{{.method}}({{if .hasReq}}in{{if .stream}} ,stream{{end}}{{else}}{{if .stream}}stream{{end}}{{end}})
 }
 `
-)
+
+//go:embed server.tpl
+var serverTemplate string
 
 // GenServer generates rpc server file, which is an implementation of rpc server
 func (g *Generator) GenServer(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
diff --git a/tools/goctl/rpc/generator/gensvc.go b/tools/goctl/rpc/generator/gensvc.go
index c96726fd..fcad078a 100644
--- a/tools/goctl/rpc/generator/gensvc.go
+++ b/tools/goctl/rpc/generator/gensvc.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"fmt"
 	"path/filepath"
 
@@ -11,20 +12,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-const svcTemplate = `package svc
-
-import {{.imports}}
-
-type ServiceContext struct {
-	Config config.Config
-}
-
-func NewServiceContext(c config.Config) *ServiceContext {
-	return &ServiceContext{
-		Config:c,
-	}
-}
-`
+//go:embed svc.tpl
+var svcTemplate string
 
 // GenSvc generates the servicecontext.go file, which is the resource dependency of a service,
 // such as rpc dependency, model dependency, etc.
diff --git a/tools/goctl/rpc/generator/logic.tpl b/tools/goctl/rpc/generator/logic.tpl
new file mode 100644
index 00000000..b4ca2a1b
--- /dev/null
+++ b/tools/goctl/rpc/generator/logic.tpl
@@ -0,0 +1,24 @@
+package logic
+
+import (
+	"context"
+
+	{{.imports}}
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type {{.logicName}} struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} {
+	return &{{.logicName}}{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+{{.functions}}
diff --git a/tools/goctl/rpc/generator/main.tpl b/tools/goctl/rpc/generator/main.tpl
new file mode 100644
index 00000000..4e13a7c5
--- /dev/null
+++ b/tools/goctl/rpc/generator/main.tpl
@@ -0,0 +1,37 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+
+	{{.imports}}
+
+	"github.com/zeromicro/go-zero/core/conf"
+	"github.com/zeromicro/go-zero/core/service"
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/reflection"
+)
+
+var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
+
+func main() {
+	flag.Parse()
+
+	var c config.Config
+	conf.MustLoad(*configFile, &c)
+	ctx := svc.NewServiceContext(c)
+	svr := server.New{{.serviceNew}}Server(ctx)
+
+	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
+		{{.pkg}}.Register{{.service}}Server(grpcServer, svr)
+
+		if c.Mode == service.DevMode || c.Mode == service.TestMode {
+			reflection.Register(grpcServer)
+		}
+	})
+	defer s.Stop()
+
+	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
+	s.Start()
+}
diff --git a/tools/goctl/rpc/generator/prototmpl.go b/tools/goctl/rpc/generator/prototmpl.go
index e858e9d8..f60aa031 100644
--- a/tools/goctl/rpc/generator/prototmpl.go
+++ b/tools/goctl/rpc/generator/prototmpl.go
@@ -1,6 +1,7 @@
 package generator
 
 import (
+	_ "embed"
 	"path/filepath"
 	"strings"
 
@@ -9,23 +10,8 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
 )
 
-const rpcTemplateText = `syntax = "proto3";
-
-package {{.package}};
-option go_package="./{{.package}}";
-
-message Request {
-  string ping = 1;
-}
-
-message Response {
-  string pong = 1;
-}
-
-service {{.serviceName}} {
-  rpc Ping(Request) returns(Response);
-}
-`
+//go:embed rpc.tpl
+var rpcTemplateText string
 
 // ProtoTmpl returns a sample of a proto file
 func ProtoTmpl(out string) error {
diff --git a/tools/goctl/rpc/generator/rpc.tpl b/tools/goctl/rpc/generator/rpc.tpl
new file mode 100644
index 00000000..76daa944
--- /dev/null
+++ b/tools/goctl/rpc/generator/rpc.tpl
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package {{.package}};
+option go_package="./{{.package}}";
+
+message Request {
+  string ping = 1;
+}
+
+message Response {
+  string pong = 1;
+}
+
+service {{.serviceName}} {
+  rpc Ping(Request) returns(Response);
+}
diff --git a/tools/goctl/rpc/generator/server.tpl b/tools/goctl/rpc/generator/server.tpl
new file mode 100644
index 00000000..84a2f9c1
--- /dev/null
+++ b/tools/goctl/rpc/generator/server.tpl
@@ -0,0 +1,22 @@
+{{.head}}
+
+package server
+
+import (
+	{{if .notStream}}"context"{{end}}
+
+	{{.imports}}
+)
+
+type {{.server}}Server struct {
+	svcCtx *svc.ServiceContext
+	{{.unimplementedServer}}
+}
+
+func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server {
+	return &{{.server}}Server{
+		svcCtx: svcCtx,
+	}
+}
+
+{{.funcs}}
diff --git a/tools/goctl/rpc/generator/svc.tpl b/tools/goctl/rpc/generator/svc.tpl
new file mode 100644
index 00000000..cf2b47af
--- /dev/null
+++ b/tools/goctl/rpc/generator/svc.tpl
@@ -0,0 +1,13 @@
+package svc
+
+import {{.imports}}
+
+type ServiceContext struct {
+	Config config.Config
+}
+
+func NewServiceContext(c config.Config) *ServiceContext {
+	return &ServiceContext{
+		Config:c,
+	}
+}


Test patch:
diff --git a/core/collection/cache_test.go b/core/collection/cache_test.go
index bf30ee71..7fc0f465 100644
--- a/core/collection/cache_test.go
+++ b/core/collection/cache_test.go
@@ -18,7 +18,7 @@ func TestCacheSet(t *testing.T) {
 	assert.Nil(t, err)
 
 	cache.Set("first", "first element")
-	cache.Set("second", "second element")
+	cache.SetWithExpire("second", "second element", time.Second*3)
 
 	value, ok := cache.Get("first")
 	assert.True(t, ok)
diff --git a/core/collection/timingwheel_test.go b/core/collection/timingwheel_test.go
index 93e669b2..6d51a094 100644
--- a/core/collection/timingwheel_test.go
+++ b/core/collection/timingwheel_test.go
@@ -28,7 +28,6 @@ func TestTimingWheel_Drain(t *testing.T) {
 	ticker := timex.NewFakeTicker()
 	tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
 	}, ticker)
-	defer tw.Stop()
 	tw.SetTimer("first", 3, testStep*4)
 	tw.SetTimer("second", 5, testStep*7)
 	tw.SetTimer("third", 7, testStep*7)
@@ -56,6 +55,8 @@ func TestTimingWheel_Drain(t *testing.T) {
 	})
 	time.Sleep(time.Millisecond * 100)
 	assert.Equal(t, 0, count)
+	tw.Stop()
+	assert.Equal(t, ErrClosed, tw.Drain(func(key, value interface{}) {}))
 }
 
 func TestTimingWheel_SetTimerSoon(t *testing.T) {
@@ -102,6 +103,13 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
 	})
 }
 
+func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
+	ticker := timex.NewFakeTicker()
+	tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
+	tw.Stop()
+	assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
+}
+
 func TestTimingWheel_MoveTimer(t *testing.T) {
 	run := syncx.NewAtomicBool()
 	ticker := timex.NewFakeTicker()
@@ -111,7 +119,6 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
 		assert.Equal(t, 3, v.(int))
 		ticker.Done()
 	}, ticker)
-	defer tw.Stop()
 	tw.SetTimer("any", 3, testStep*4)
 	tw.MoveTimer("any", testStep*7)
 	tw.MoveTimer("any", -testStep)
@@ -125,6 +132,8 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
 	}
 	assert.Nil(t, ticker.Wait(waitTime))
 	assert.True(t, run.True())
+	tw.Stop()
+	assert.Equal(t, ErrClosed, tw.MoveTimer("any", time.Millisecond))
 }
 
 func TestTimingWheel_MoveTimerSoon(t *testing.T) {
@@ -175,6 +184,7 @@ func TestTimingWheel_RemoveTimer(t *testing.T) {
 		ticker.Tick()
 	}
 	tw.Stop()
+	assert.Equal(t, ErrClosed, tw.RemoveTimer("any"))
 }
 
 func TestTimingWheel_SetTimer(t *testing.T) {
diff --git a/rest/handler/cryptionhandler_test.go b/rest/handler/cryptionhandler_test.go
index 2957d193..0d9fba47 100644
--- a/rest/handler/cryptionhandler_test.go
+++ b/rest/handler/cryptionhandler_test.go
@@ -5,6 +5,7 @@ import (
 	"encoding/base64"
 	"io/ioutil"
 	"log"
+	"math/rand"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -116,3 +117,18 @@ func TestCryptionHandler_Hijack(t *testing.T) {
 		writer.Hijack()
 	})
 }
+
+func TestCryptionHandler_ContentTooLong(t *testing.T) {
+	handler := CryptionHandler(aesKey)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	}))
+	svr := httptest.NewServer(handler)
+	defer svr.Close()
+
+	body := make([]byte, maxBytes+1)
+	rand.Read(body)
+	req, err := http.NewRequest(http.MethodPost, svr.URL, bytes.NewReader(body))
+	assert.Nil(t, err)
+	resp, err := http.DefaultClient.Do(req)
+	assert.Nil(t, err)
+	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+}
diff --git a/rest/httpc/internal/loginterceptor_test.go b/rest/httpc/internal/loginterceptor_test.go
index 39d445d3..04ca471d 100644
--- a/rest/httpc/internal/loginterceptor_test.go
+++ b/rest/httpc/internal/loginterceptor_test.go
@@ -16,8 +16,8 @@ func TestLogInterceptor(t *testing.T) {
 	assert.Nil(t, err)
 	req, handler := LogInterceptor(req)
 	resp, err := http.DefaultClient.Do(req)
+	handler(resp, err)
 	assert.Nil(t, err)
-	handler(resp)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 }
 
@@ -30,7 +30,22 @@ func TestLogInterceptorServerError(t *testing.T) {
 	assert.Nil(t, err)
 	req, handler := LogInterceptor(req)
 	resp, err := http.DefaultClient.Do(req)
+	handler(resp, err)
 	assert.Nil(t, err)
-	handler(resp)
 	assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
 }
+
+func TestLogInterceptorServerClosed(t *testing.T) {
+	svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusInternalServerError)
+	}))
+	defer svr.Close()
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	svr.Close()
+	req, handler := LogInterceptor(req)
+	resp, err := http.DefaultClient.Do(req)
+	handler(resp, err)
+	assert.NotNil(t, err)
+	assert.Nil(t, resp)
+}
diff --git a/rest/httpc/requests_test.go b/rest/httpc/requests_test.go
index 0deeff2a..815649af 100644
--- a/rest/httpc/requests_test.go
+++ b/rest/httpc/requests_test.go
@@ -12,9 +12,9 @@ func TestDo(t *testing.T) {
 	svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 	}))
 	defer svr.Close()
-	_, err := Get("foo", "tcp://bad request")
-	assert.NotNil(t, err)
-	resp, err := Get("foo", svr.URL)
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	resp, err := DoRequest(req)
 	assert.Nil(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 }
@@ -22,9 +22,10 @@ func TestDo(t *testing.T) {
 func TestDoNotFound(t *testing.T) {
 	svr := httptest.NewServer(http.NotFoundHandler())
 	defer svr.Close()
-	_, err := Post("foo", "tcp://bad request", "application/json", nil)
-	assert.NotNil(t, err)
-	resp, err := Post("foo", svr.URL, "application/json", nil)
+	req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
+	assert.Nil(t, err)
+	req.Header.Set("Content-Type", "application/json")
+	resp, err := DoRequest(req)
 	assert.Nil(t, err)
 	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
 }
@@ -34,7 +35,7 @@ func TestDoMoved(t *testing.T) {
 	defer svr.Close()
 	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
 	assert.Nil(t, err)
-	_, err = Do("foo", req)
+	_, err = DoRequest(req)
 	// too many redirects
 	assert.NotNil(t, err)
 }
diff --git a/rest/httpc/responses_test.go b/rest/httpc/responses_test.go
index 13e4acc6..aed53c9f 100644
--- a/rest/httpc/responses_test.go
+++ b/rest/httpc/responses_test.go
@@ -20,7 +20,9 @@ func TestParse(t *testing.T) {
 		w.Write([]byte(`{"name":"kevin","value":100}`))
 	}))
 	defer svr.Close()
-	resp, err := Get("foo", svr.URL)
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	resp, err := DoRequest(req)
 	assert.Nil(t, err)
 	assert.Nil(t, Parse(resp, &val))
 	assert.Equal(t, "bar", val.Foo)
@@ -37,7 +39,9 @@ func TestParseHeaderError(t *testing.T) {
 		w.Header().Set(contentType, applicationJson)
 	}))
 	defer svr.Close()
-	resp, err := Get("foo", svr.URL)
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	resp, err := DoRequest(req)
 	assert.Nil(t, err)
 	assert.NotNil(t, Parse(resp, &val))
 }
@@ -51,7 +55,9 @@ func TestParseNoBody(t *testing.T) {
 		w.Header().Set(contentType, applicationJson)
 	}))
 	defer svr.Close()
-	resp, err := Get("foo", svr.URL)
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	resp, err := DoRequest(req)
 	assert.Nil(t, err)
 	assert.Nil(t, Parse(resp, &val))
 	assert.Equal(t, "bar", val.Foo)
diff --git a/rest/httpc/service_test.go b/rest/httpc/service_test.go
index f92177a4..ea259796 100644
--- a/rest/httpc/service_test.go
+++ b/rest/httpc/service_test.go
@@ -14,7 +14,7 @@ func TestNamedService_Do(t *testing.T) {
 	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
 	assert.Nil(t, err)
 	service := NewService("foo")
-	_, err = service.Do(req)
+	_, err = service.DoRequest(req)
 	// too many redirects
 	assert.NotNil(t, err)
 }
@@ -28,7 +28,9 @@ func TestNamedService_Get(t *testing.T) {
 		r.Header.Set("foo", "bar")
 		return r
 	})
-	resp, err := service.Get(svr.URL)
+	req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
+	assert.Nil(t, err)
+	resp, err := service.DoRequest(req)
 	assert.Nil(t, err)
 	assert.Equal(t, http.StatusOK, resp.StatusCode)
 	assert.Equal(t, "bar", resp.Header.Get("foo"))
@@ -38,9 +40,10 @@ func TestNamedService_Post(t *testing.T) {
 	svr := httptest.NewServer(http.NotFoundHandler())
 	defer svr.Close()
 	service := NewService("foo")
-	_, err := service.Post("tcp://bad request", "application/json", nil)
-	assert.NotNil(t, err)
-	resp, err := service.Post(svr.URL, "application/json", nil)
+	req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
+	assert.Nil(t, err)
+	req.Header.Set("Content-Type", "application/json")
+	resp, err := service.DoRequest(req)
 	assert.Nil(t, err)
 	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
 }
diff --git a/tools/goctl/api/parser/g4/ast/apiparser_test.go b/tools/goctl/api/parser/g4/ast/apiparser_test.go
new file mode 100644
index 00000000..8110c279
--- /dev/null
+++ b/tools/goctl/api/parser/g4/ast/apiparser_test.go
@@ -0,0 +1,99 @@
+package ast
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+func Test_ImportCycle(t *testing.T) {
+	const (
+		mainFilename = "main.api"
+		subAFilename = "a.api"
+		subBFilename = "b.api"
+		mainSrc      = `import "./a.api"`
+		subASrc      = `import "./b.api"`
+		subBSrc      = `import "./a.api"`
+	)
+	var err error
+	dir := pathx.MustTempDir()
+	defer os.RemoveAll(dir)
+
+	mainPath := filepath.Join(dir, mainFilename)
+	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0777)
+	require.NoError(t, err)
+	subAPath := filepath.Join(dir, subAFilename)
+	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0777)
+	require.NoError(t, err)
+	subBPath := filepath.Join(dir, subBFilename)
+	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0777)
+	require.NoError(t, err)
+
+	_, err = NewParser().Parse(mainPath)
+	assert.ErrorIs(t, err, ErrImportCycleNotAllowed)
+}
+
+func Test_MultiImportedShouldAllowed(t *testing.T) {
+	const (
+		mainFilename = "main.api"
+		subAFilename = "a.api"
+		subBFilename = "b.api"
+		mainSrc      = "import \"./b.api\"\n" +
+			"import \"./a.api\"\n" +
+			"type Main { b B `json:\"b\"`}"
+		subASrc = "import \"./b.api\"\n" +
+			"type A { b B `json:\"b\"`}\n"
+		subBSrc = `type B{}`
+	)
+	var err error
+	dir := pathx.MustTempDir()
+	defer os.RemoveAll(dir)
+
+	mainPath := filepath.Join(dir, mainFilename)
+	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0777)
+	require.NoError(t, err)
+	subAPath := filepath.Join(dir, subAFilename)
+	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0777)
+	require.NoError(t, err)
+	subBPath := filepath.Join(dir, subBFilename)
+	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0777)
+	require.NoError(t, err)
+
+	_, err = NewParser().Parse(mainPath)
+	assert.NoError(t, err)
+}
+
+func Test_RedundantDeclarationShouldNotBeAllowed(t *testing.T) {
+	const (
+		mainFilename = "main.api"
+		subAFilename = "a.api"
+		subBFilename = "b.api"
+		mainSrc      = "import \"./a.api\"\n" +
+			"import \"./b.api\"\n"
+		subASrc = `import "./b.api"
+							 type A{}`
+		subBSrc = `type A{}`
+	)
+	var err error
+	dir := pathx.MustTempDir()
+	defer os.RemoveAll(dir)
+
+	mainPath := filepath.Join(dir, mainFilename)
+	err = ioutil.WriteFile(mainPath, []byte(mainSrc), 0777)
+	require.NoError(t, err)
+	subAPath := filepath.Join(dir, subAFilename)
+	err = ioutil.WriteFile(subAPath, []byte(subASrc), 0777)
+	require.NoError(t, err)
+	subBPath := filepath.Join(dir, subBFilename)
+	err = ioutil.WriteFile(subBPath, []byte(subBSrc), 0777)
+	require.NoError(t, err)
+
+	_, err = NewParser().Parse(mainPath)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "duplicate type declaration")
+}