Compare commits

..

21 Commits

Author SHA1 Message Date
Keson
e76f44a35b reactor rpc (#179)
* reactor rpc generation

* update flag

* update command

* update command

* update unit test

* delete test file

* optimize code

* update doc

* update gen pb

* rename target dir

* update mysql data type convert rule

* add done flag

* optimize req/reply parameter

* optimize req/reply parameter

* remove waste code

* remove duplicate parameter

* format code

* format code

* optimize naming

* reactor rpcv2 to rpc

* remove new line

* format code

* rename underline to snake

* reactor getParentPackage

* remove debug log

* reactor background
2020-11-05 14:12:47 +08:00
kevin
c9ec22d5f4 add https listen and serve 2020-11-05 11:56:40 +08:00
Dashuang Li
afffc1048b fix url 404 (#180) 2020-11-04 12:03:07 +08:00
kevin
d0b76b1d9a move redistest into redis package 2020-11-03 16:35:34 +08:00
kevin
b004b070d7 refine tests 2020-11-02 17:51:33 +08:00
kevin
677d581bd1 update doc 2020-11-02 17:05:09 +08:00
kingxt
b776468e69 route support no request and response (#178)
* add more test and support no request and response

* fix slash when run on windows

* optimize test
2020-11-02 13:48:16 +08:00
kevin
4c9315e984 add more tests 2020-10-31 22:10:11 +08:00
kevin
668a7011c4 add more tests 2020-10-31 20:11:12 +08:00
吴亲库里
cc07a1d69b Update sharedcalls.go (#174)
Removes unused parameters
2020-10-31 19:40:07 +08:00
kevin
7f99a3baa8 add gitee url 2020-10-31 13:58:33 +08:00
kevin
9504418462 update doc 2020-10-31 12:41:29 +08:00
kevin
b144a2335c update bookstore example for generation prototype 2020-10-31 11:42:44 +08:00
kevin
7b9ed7a313 update doc 2020-10-30 15:20:19 +08:00
kevin
3d2e9fcb84 remove wechat image 2020-10-30 11:57:32 +08:00
kevin
2b993424c1 update wechat qrcode 2020-10-30 11:54:06 +08:00
kevin
5e87b33b23 support https in rest 2020-10-29 17:44:51 +08:00
kevin
9b7cc43dcb update wechat qrcode 2020-10-29 15:32:08 +08:00
kevin
000b28cf84 update readme 2020-10-29 11:31:35 +08:00
kevin
9fd16cd278 add images back because of gitee not showing 2020-10-29 11:27:40 +08:00
kevin
b71429e16b add images back because of gitee not showing 2020-10-29 11:26:10 +08:00
127 changed files with 3021 additions and 3167 deletions

View File

@@ -2,20 +2,16 @@ package bloom
import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
)
func TestRedisBitSet_New_Set_Test(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redis.NewRedis(s.Addr(), redis.NodeType)
bitSet := newRedisBitSet(store, "test_key", 1024)
isSetBefore, err := bitSet.check([]uint{0})
if err != nil {
@@ -46,11 +42,10 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
}
func TestRedisBitSet_Add(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
store := redis.NewRedis(s.Addr(), redis.NodeType)
filter := New(store, "test_key", 64)
assert.Nil(t, filter.Add([]byte("hello")))
assert.Nil(t, filter.Add([]byte("world")))
@@ -58,22 +53,3 @@ func TestRedisBitSet_Add(t *testing.T) {
assert.Nil(t, err)
assert.True(t, ok)
}
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
r, err = miniredis.Run()
if err != nil {
return nil, nil, err
}
return r, func() {
ch := make(chan lang.PlaceholderType)
go func() {
r.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
)
func TestPeriodLimit_Take(t *testing.T) {
@@ -33,16 +34,16 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
}
func testPeriodLimit(t *testing.T, opts ...LimitOption) {
s, err := miniredis.Run()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer s.Close()
defer clean()
const (
seconds = 1
total = 100
quota = 5
)
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit", opts...)
l := NewPeriodLimit(seconds, quota, store, "periodlimit", opts...)
var allowed, hitQuota, overQuota int
for i := 0; i < total; i++ {
val, err := l.Take("first")

View File

@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
)
func init() {
@@ -44,16 +45,16 @@ func TestTokenLimit_Rescue(t *testing.T) {
}
func TestTokenLimit_Take(t *testing.T) {
s, err := miniredis.Run()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer s.Close()
defer clean()
const (
total = 100
rate = 5
burst = 10
)
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int
for i := 0; i < total; i++ {
time.Sleep(time.Second / time.Duration(total))
@@ -66,16 +67,16 @@ func TestTokenLimit_Take(t *testing.T) {
}
func TestTokenLimit_TakeBurst(t *testing.T) {
s, err := miniredis.Run()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer s.Close()
defer clean()
const (
total = 100
rate = 5
burst = 10
)
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int
for i := 0; i < total; i++ {
if l.Allow() {

View File

@@ -12,6 +12,7 @@ import (
"github.com/tal-tech/go-zero/core/errorx"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/syncx"
)
@@ -75,23 +76,23 @@ func (mc *mockedNode) TakeWithExpire(v interface{}, key string, query func(v int
func TestCache_SetDel(t *testing.T) {
const total = 1000
r1, clean1, err := createMiniRedis()
r1, clean1, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean1()
r2, clean2, err := createMiniRedis()
r2, clean2, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean2()
conf := ClusterConf{
{
RedisConf: redis.RedisConf{
Host: r1.Addr(),
Host: r1.Addr,
Type: redis.NodeType,
},
Weight: 100,
},
{
RedisConf: redis.RedisConf{
Host: r2.Addr(),
Host: r2.Addr,
Type: redis.NodeType,
},
Weight: 100,
@@ -123,13 +124,13 @@ func TestCache_SetDel(t *testing.T) {
func TestCache_OneNode(t *testing.T) {
const total = 1000
r, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
conf := ClusterConf{
{
RedisConf: redis.RedisConf{
Host: r.Addr(),
Host: r.Addr,
Type: redis.NodeType,
},
Weight: 100,

View File

@@ -15,6 +15,7 @@ import (
"github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/syncx"
)
@@ -26,12 +27,12 @@ func init() {
}
func TestCacheNode_DelCache(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
@@ -49,9 +50,9 @@ func TestCacheNode_DelCache(t *testing.T) {
}
func TestCacheNode_InvalidCache(t *testing.T) {
s, clean, err := createMiniRedis()
s, err := miniredis.Run()
assert.Nil(t, err)
defer clean()
defer s.Close()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
@@ -70,12 +71,12 @@ func TestCacheNode_InvalidCache(t *testing.T) {
}
func TestCacheNode_Take(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
@@ -91,18 +92,18 @@ func TestCacheNode_Take(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "value", str)
assert.Nil(t, cn.GetCache("any", &str))
val, err := s.Get("any")
val, err := store.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_TakeNotFound(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
@@ -116,18 +117,18 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
})
assert.Equal(t, errTestNotFound, err)
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
val, err := s.Get("any")
val, err := store.Get("any")
assert.Nil(t, err)
assert.Equal(t, `*`, val)
s.Set("any", "*")
store.Set("any", "*")
err = cn.Take(&str, "any", func(v interface{}) error {
return nil
})
assert.Equal(t, errTestNotFound, err)
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
s.Del("any")
store.Del("any")
var errDummy = errors.New("dummy")
err = cn.Take(&str, "any", func(v interface{}) error {
return errDummy
@@ -136,12 +137,12 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
}
func TestCacheNode_TakeWithExpire(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
@@ -157,18 +158,18 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "value", str)
assert.Nil(t, cn.GetCache("any", &str))
val, err := s.Get("any")
val, err := store.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_String(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
@@ -176,16 +177,16 @@ func TestCacheNode_String(t *testing.T) {
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
assert.Equal(t, s.Addr(), cn.String())
assert.Equal(t, store.Addr, cn.String())
}
func TestCacheValueWithBigInt(t *testing.T) {
s, clean, err := createMiniRedis()
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),

View File

@@ -2,11 +2,8 @@ package cache
import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang"
)
func TestFormatKeys(t *testing.T) {
@@ -27,22 +24,3 @@ func TestTotalWeights(t *testing.T) {
})
assert.Equal(t, 1, val)
}
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
r, err = miniredis.Run()
if err != nil {
return nil, nil, err
}
return r, func() {
ch := make(chan lang.PlaceholderType)
go func() {
r.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -11,7 +11,6 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"github.com/stretchr/testify/assert"
@@ -19,6 +18,7 @@ import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/mongo"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
)
func init() {
@@ -27,12 +27,10 @@ func init() {
func TestStat(t *testing.T) {
resetStats()
s, err := miniredis.Run()
if err != nil {
t.Error(err)
}
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
@@ -73,12 +71,10 @@ func TestStatCacheFails(t *testing.T) {
func TestStatDbFails(t *testing.T) {
resetStats()
s, err := miniredis.Run()
if err != nil {
t.Error(err)
}
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
@@ -97,12 +93,10 @@ func TestStatDbFails(t *testing.T) {
func TestStatFromMemory(t *testing.T) {
resetStats()
s, err := miniredis.Run()
if err != nil {
t.Error(err)
}
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)

View File

@@ -0,0 +1,28 @@
package redistest
import (
"time"
"github.com/alicebob/miniredis"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stores/redis"
)
func CreateRedis() (r *redis.Redis, clean func(), err error) {
mr, err := miniredis.Run()
if err != nil {
return nil, nil, err
}
return redis.NewRedis(mr.Addr(), redis.NodeType), func() {
ch := make(chan lang.PlaceholderType)
go func() {
mr.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -16,11 +16,11 @@ import (
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
@@ -31,16 +31,15 @@ func init() {
func TestCachedConn_GetCache(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
var value string
err = c.GetCache("any", &value)
assert.Equal(t, ErrNotFound, err)
s.Set("any", `"value"`)
r.Set("any", `"value"`)
err = c.GetCache("any", &value)
assert.Nil(t, err)
assert.Equal(t, "value", value)
@@ -48,11 +47,10 @@ func TestCachedConn_GetCache(t *testing.T) {
func TestStat(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 10; i++ {
@@ -72,15 +70,14 @@ func TestStat(t *testing.T) {
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewConn(dummySqlConn{}, cache.CacheConf{
{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Host: r.Addr,
Type: redis.NodeType,
},
Weight: 100,
@@ -122,11 +119,10 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
@@ -210,16 +206,13 @@ func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
},
}
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resetStats()
s.FlushAll()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
@@ -256,11 +249,10 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
for k, v := range caches {
t.Run(k+"/"+v, func(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
@@ -312,11 +304,10 @@ func TestStatCacheFails(t *testing.T) {
func TestStatDbFails(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 20; i++ {
@@ -334,11 +325,10 @@ func TestStatDbFails(t *testing.T) {
func TestStatFromMemory(t *testing.T) {
resetStats()
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
var all sync.WaitGroup
@@ -393,7 +383,7 @@ func TestStatFromMemory(t *testing.T) {
}
func TestCachedConnQueryRow(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
@@ -404,7 +394,6 @@ func TestCachedConnQueryRow(t *testing.T) {
var conn trackedConn
var user string
var ran bool
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
ran = true
@@ -412,7 +401,7 @@ func TestCachedConnQueryRow(t *testing.T) {
return nil
})
assert.Nil(t, err)
actualValue, err := s.Get(key)
actualValue, err := r.Get(key)
assert.Nil(t, err)
var actual string
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
@@ -422,7 +411,7 @@ func TestCachedConnQueryRow(t *testing.T) {
}
func TestCachedConnQueryRowFromCache(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
@@ -433,7 +422,6 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
var conn trackedConn
var user string
var ran bool
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
assert.Nil(t, c.SetCache(key, value))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
@@ -442,7 +430,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
return nil
})
assert.Nil(t, err)
actualValue, err := s.Get(key)
actualValue, err := r.Get(key)
assert.Nil(t, err)
var actual string
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
@@ -452,7 +440,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
}
func TestQueryRowNotFound(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
@@ -460,7 +448,6 @@ func TestQueryRowNotFound(t *testing.T) {
var conn trackedConn
var user string
var ran int
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
for i := 0; i < 20; i++ {
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
@@ -473,12 +460,11 @@ func TestQueryRowNotFound(t *testing.T) {
}
func TestCachedConnExec(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err)
@@ -486,24 +472,23 @@ func TestCachedConnExec(t *testing.T) {
}
func TestCachedConnExecDropCache(t *testing.T) {
s, clean, err := createMiniRedis()
r, err := miniredis.Run()
assert.Nil(t, err)
defer clean()
defer r.Close()
const (
key = "user"
value = "any"
)
var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
c := NewNodeConn(&conn, redis.NewRedis(r.Addr(), redis.NodeType), cache.WithExpiry(time.Second*30))
assert.Nil(t, c.SetCache(key, value))
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
return conn.Exec("delete from user_table where id='kevin'")
}, key)
assert.Nil(t, err)
assert.True(t, conn.execValue)
_, err = s.Get(key)
_, err = r.Get(key)
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
return nil, errors.New("foo")
@@ -524,12 +509,11 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
}
func TestCachedConnQueryRows(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
var users []string
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
@@ -538,12 +522,11 @@ func TestCachedConnQueryRows(t *testing.T) {
}
func TestCachedConnTransact(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
err = c.Transact(func(session sqlx.Session) error {
return nil
@@ -553,7 +536,7 @@ func TestCachedConnTransact(t *testing.T) {
}
func TestQueryRowNoCache(t *testing.T) {
s, clean, err := createMiniRedis()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
@@ -563,7 +546,6 @@ func TestQueryRowNoCache(t *testing.T) {
)
var user string
var ran bool
r := redis.NewRedis(s.Addr(), redis.NodeType)
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
user = value
ran = true
@@ -639,22 +621,3 @@ func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
c.transactValue = true
return c.dummySqlConn.Transact(fn)
}
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
r, err = miniredis.Run()
if err != nil {
return nil, nil, err
}
return r, func() {
ch := make(chan lang.PlaceholderType)
go func() {
r.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -33,7 +33,7 @@ func NewSharedCalls() SharedCalls {
}
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
c, done := g.createCall(key, fn)
c, done := g.createCall(key)
if done {
return c.val, c.err
}
@@ -43,7 +43,7 @@ func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{
}
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
c, done := g.createCall(key, fn)
c, done := g.createCall(key)
if done {
return c.val, false, c.err
}
@@ -52,7 +52,7 @@ func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val inte
return c.val, true, c.err
}
func (g *sharedGroup) createCall(key string, fn func() (interface{}, error)) (c *call, done bool) {
func (g *sharedGroup) createCall(key string) (c *call, done bool) {
g.lock.Lock()
if c, ok := g.calls[key]; ok {
g.lock.Unlock()

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
doc/images/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

BIN
doc/images/benchmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
doc/images/go-zero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
doc/images/resilience.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -8,9 +8,9 @@ import (
"fmt"
"bookstore/rpc/add/internal/config"
add "bookstore/rpc/add/internal/pb"
"bookstore/rpc/add/internal/server"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/core/logx"

View File

@@ -8,13 +8,15 @@ package adder
import (
"context"
add "bookstore/rpc/add/pb"
add "bookstore/rpc/add/internal/pb"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
AddReq = add.AddReq
AddResp = add.AddResp
Adder interface {
Add(ctx context.Context, in *AddReq) (*AddResp, error)
}
@@ -31,33 +33,6 @@ func NewAdder(cli zrpc.Client) Adder {
}
func (m *defaultAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
var request add.AddReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := add.NewAdderClient(m.cli.Conn())
resp, err := client.Add(ctx, &request)
if err != nil {
return nil, err
}
var ret AddResp
bts, err = jsonx.Marshal(resp)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &ret)
if err != nil {
return nil, errJsonConvert
}
return &ret, nil
adder := add.NewAdderClient(m.cli.Conn())
return adder.Add(ctx, in)
}

View File

@@ -1,19 +0,0 @@
// Code generated by goctl. DO NOT EDIT!
// Source: add.proto
package adder
import "errors"
var errJsonConvert = errors.New("json convert error")
type (
AddReq struct {
Book string `json:"book,omitempty"`
Price int64 `json:"price,omitempty"`
}
AddResp struct {
Ok bool `json:"ok,omitempty"`
}
)

View File

@@ -3,8 +3,8 @@ package logic
import (
"context"
add "bookstore/rpc/add/internal/pb"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"bookstore/rpc/model"
"github.com/tal-tech/go-zero/core/logx"

View File

@@ -14,13 +14,13 @@ It has these top-level messages:
*/
package add
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
"fmt"
"math"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.

View File

@@ -7,8 +7,8 @@ import (
"context"
"bookstore/rpc/add/internal/logic"
add "bookstore/rpc/add/internal/pb"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
)
type AdderServer struct {

View File

@@ -8,9 +8,9 @@ import (
"fmt"
"bookstore/rpc/check/internal/config"
check "bookstore/rpc/check/internal/pb"
"bookstore/rpc/check/internal/server"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/core/logx"

View File

@@ -8,13 +8,15 @@ package checker
import (
"context"
check "bookstore/rpc/check/pb"
check "bookstore/rpc/check/internal/pb"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
CheckReq = check.CheckReq
CheckResp = check.CheckResp
Checker interface {
Check(ctx context.Context, in *CheckReq) (*CheckResp, error)
}
@@ -31,33 +33,6 @@ func NewChecker(cli zrpc.Client) Checker {
}
func (m *defaultChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
var request check.CheckReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := check.NewCheckerClient(m.cli.Conn())
resp, err := client.Check(ctx, &request)
if err != nil {
return nil, err
}
var ret CheckResp
bts, err = jsonx.Marshal(resp)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &ret)
if err != nil {
return nil, errJsonConvert
}
return &ret, nil
checker := check.NewCheckerClient(m.cli.Conn())
return checker.Check(ctx, in)
}

View File

@@ -1,19 +0,0 @@
// Code generated by goctl. DO NOT EDIT!
// Source: check.proto
package checker
import "errors"
var errJsonConvert = errors.New("json convert error")
type (
CheckReq struct {
Book string `json:"book,omitempty"`
}
CheckResp struct {
Found bool `json:"found,omitempty"`
Price int64 `json:"price,omitempty"`
}
)

View File

@@ -3,8 +3,8 @@ package logic
import (
"context"
check "bookstore/rpc/check/internal/pb"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"github.com/tal-tech/go-zero/core/logx"
)

View File

@@ -14,13 +14,13 @@ It has these top-level messages:
*/
package check
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
"fmt"
"math"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.

View File

@@ -7,8 +7,8 @@ import (
"context"
"bookstore/rpc/check/internal/logic"
check "bookstore/rpc/check/internal/pb"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
)
type CheckerServer struct {

View File

@@ -1,4 +1,4 @@
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
<img align="right" width="150px" src="doc/images/go-zero.png">
# go-zero
@@ -25,7 +25,7 @@ Advantages of go-zero:
* auto validate the request parameters from clients
* plenty of builtin microservice management and concurrent toolkits
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
<img src="doc/images/architecture-en.png" alt="Architecture" width="1500" />
## 1. Backgrounds of go-zero
@@ -76,7 +76,7 @@ go-zero is a web and rpc framework that integrates lots of engineering practices
As below, go-zero protects the system with couple layers and mechanisms:
![Resilience](https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/resilience-en.png)
![Resilience](doc/images/resilience-en.png)
## 4. Future development plans of go-zero
@@ -121,19 +121,17 @@ go get -u github.com/tal-tech/go-zero
}
service greet-api {
@server(
handler: GreetHandler
)
@handler GreetHandler
get /greet/from/:name(Request) returns (Response);
}
```
the .api files also can be generate by goctl, like below:
```shell
goctl api -o greet.api
goctl api -o greet.api
```
3. generate the go server side code
```shell
@@ -202,7 +200,7 @@ go get -u github.com/tal-tech/go-zero
## 7. Benchmark
![benchmark](https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/benchmark.png)
![benchmark](doc/images/benchmark.png)
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)

View File

@@ -1,4 +1,4 @@
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
<img align="right" width="150px" src="doc/images/go-zero.png">
# go-zero
@@ -25,7 +25,7 @@ go-zero 包含极简的 API 定义和生成工具 goctl可以根据定义的
* 自动校验客户端请求参数合法性
* 大量微服务治理和并发工具包
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture.png" alt="架构图" width="1500" />
<img src="doc/images/architecture.png" alt="架构图" width="1500" />
## 1. go-zero 框架背景
@@ -77,7 +77,9 @@ go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有
如下图,我们从多个层面保障了整体服务的高可用:
![弹性设计](https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/resilience.jpg)
![弹性设计](doc/images/resilience.jpg)
觉得不错的话,别忘 **star** 👏
## 4. Installation
@@ -93,7 +95,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
[快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/frame/bookstore.md)
1. 安装 goctl 工具
@@ -148,7 +150,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
## 6. Benchmark
![benchmark](https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/benchmark.png)
![benchmark](doc/images/benchmark.png)
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
@@ -160,7 +162,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
* awesome 系列
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/docs/frame/bookstore.md)
* [goctl 使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
* [通过 MapReduce 降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
@@ -170,18 +172,22 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
* [快速构建 jwt 鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
## 9. 微信交流群
加群之前有劳给一个 star一个小小的 star 是作者们回答海量问题的动力。
## 8. 微信交流群
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
如果您发现 bug 请及时提 issue我们会尽快确认并修改。
如果您发现 ***bug*** 请及时提 ***issue***,我们会尽快确认并修改。
<!-- 扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢!-->
为了防止广告用户、识别技术同行,请 ***star*** 后加我时注明 **github** 当前 ***star*** 数,我再拉进 **go-zero** 群,谢!
开源中国年度评选给go-zero投上一票[https://www.oschina.net/p/go-zero](https://www.oschina.net/p/go-zero)
加我之前有劳点一下 ***star***,一个小小的 ***star*** 是作者们回答海量问题的动力🤝
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
<img src="https://gitee.com/kevwan/static/raw/master/images/wechat.jpg" alt="wechat" width="300" />
项目地址:[https://github.com/tal-tech/go-zero](https://github.com/tal-tech/go-zero)
码云地址:[https://gitee.com/kevwan/go-zero](https://gitee.com/kevwan/go-zero) (国内用户可访问gitee每日自动从github同步代码)
开源中国年度评选,给 **go-zero** 投上一票:[https://www.oschina.net/p/go-zero](https://www.oschina.net/p/go-zero)

View File

@@ -18,7 +18,7 @@ type (
PrivateKeys []PrivateKeyConf
}
// why not name it as Conf, because we need to consider usage like:
// Why not name it as Conf, because we need to consider usage like:
// type Config struct {
// zrpc.RpcConf
// rest.RestConf
@@ -28,9 +28,11 @@ type (
service.ServiceConf
Host string `json:",default=0.0.0.0"`
Port int
Verbose bool `json:",optional"`
MaxConns int `json:",default=10000"`
MaxBytes int64 `json:",default=1048576,range=[0:8388608]"`
CertFile string `json:",optional"`
KeyFile string `json:",optional"`
Verbose bool `json:",optional"`
MaxConns int `json:",default=10000"`
MaxBytes int64 `json:",default=1048576,range=[0:8388608]"`
// milliseconds
Timeout int64 `json:",default=3000"`
CpuThreshold int64 `json:",default=900,range=[0:1000]"`

View File

@@ -65,7 +65,11 @@ func (s *engine) StartWithRouter(router httpx.Router) error {
return err
}
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
if len(s.conf.CertFile) == 0 && len(s.conf.KeyFile) == 0 {
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
}
return internal.StartHttps(s.conf.Host, s.conf.Port, s.conf.CertFile, s.conf.KeyFile, router)
}
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,

View File

@@ -19,9 +19,7 @@ func TestParseForm(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
assert.Nil(t, err)
err = Parse(r, &v)
assert.Nil(t, err)
assert.Nil(t, Parse(r, &v))
assert.Equal(t, "hello", v.Name)
assert.Equal(t, 18, v.Age)
assert.Equal(t, 3.4, v.Percent)
@@ -97,8 +95,44 @@ Content-Disposition: form-data; name="age"
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
err := Parse(r, &v)
assert.Nil(t, err)
assert.Nil(t, Parse(r, &v))
assert.Equal(t, "kevin", v.Name)
assert.Equal(t, 18, v.Age)
}
func TestParseMultipartFormWrongBoundary(t *testing.T) {
var v struct {
Name string `form:"name"`
Age int `form:"age"`
}
body := strings.Replace(`----------------------------22047761238815478001938
Content-Disposition: form-data; name="name"
kevin
----------------------------22047761238815478001938
Content-Disposition: form-data; name="age"
18
----------------------------22047761238815478001938--`, "\n", "\r\n", -1)
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
assert.NotNil(t, Parse(r, &v))
}
func TestParseJsonBody(t *testing.T) {
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
body := `{"name":"kevin", "age": 18}`
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
r.Header.Set(ContentType, ApplicationJson)
assert.Nil(t, Parse(r, &v))
assert.Equal(t, "kevin", v.Name)
assert.Equal(t, 18, v.Age)
}
@@ -111,9 +145,7 @@ func TestParseRequired(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello", nil)
assert.Nil(t, err)
err = Parse(r, &v)
assert.NotNil(t, err)
assert.NotNil(t, Parse(r, &v))
}
func TestParseOptions(t *testing.T) {
@@ -123,9 +155,7 @@ func TestParseOptions(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?pos=4", nil)
assert.Nil(t, err)
err = Parse(r, &v)
assert.NotNil(t, err)
assert.NotNil(t, Parse(r, &v))
}
func BenchmarkParseRaw(b *testing.B) {

View File

@@ -23,5 +23,5 @@ func WithPathVars(r *http.Request, params map[string]string) *http.Request {
type contextKey string
func (c contextKey) String() string {
return "rest/internal/context context key" + string(c)
return "rest/internal/context key: " + string(c)
}

View File

@@ -0,0 +1,32 @@
package context
import (
"context"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVars(t *testing.T) {
expect := map[string]string{
"a": "1",
"b": "2",
}
r, err := http.NewRequest(http.MethodGet, "/", nil)
assert.Nil(t, err)
r = r.WithContext(context.WithValue(context.Background(), pathVars, expect))
assert.EqualValues(t, expect, Vars(r))
}
func TestVarsNil(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "/", nil)
assert.Nil(t, err)
assert.Nil(t, Vars(r))
}
func TestContextKey(t *testing.T) {
ck := contextKey("hello")
assert.True(t, strings.Contains(ck.String(), "hello"))
}

View File

@@ -12,7 +12,8 @@ import (
func StartHttp(host string, port int, handler http.Handler) error {
addr := fmt.Sprintf("%s:%d", host, port)
server := buildHttpServer(addr, handler)
return StartServer(server)
gracefulOnShutdown(server)
return server.ListenAndServe()
}
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
@@ -20,18 +21,12 @@ func StartHttps(host string, port int, certFile, keyFile string, handler http.Ha
if server, err := buildHttpsServer(addr, handler, certFile, keyFile); err != nil {
return err
} else {
return StartServer(server)
gracefulOnShutdown(server)
// certFile and keyFile are set in buildHttpsServer
return server.ListenAndServeTLS("", "")
}
}
func StartServer(srv *http.Server) error {
proc.AddWrapUpListener(func() {
srv.Shutdown(context.Background())
})
return srv.ListenAndServe()
}
func buildHttpServer(addr string, handler http.Handler) *http.Server {
return &http.Server{Addr: addr, Handler: handler}
}
@@ -49,3 +44,9 @@ func buildHttpsServer(addr string, handler http.Handler, certFile, keyFile strin
TLSConfig: &config,
}, nil
}
func gracefulOnShutdown(srv *http.Server) {
proc.AddWrapUpListener(func() {
srv.Shutdown(context.Background())
})
}

View File

@@ -185,6 +185,25 @@ service A-api {
}
`
const apiRouteTest = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + `
}
service A-api {
@handler NormalHandler
get /greet/from/:name(Request) returns (Response)
@handler NoResponseHandler
get /greet/from/:sex(Request)
@handler NoRequestHandler
get /greet/from/request returns (Response)
@handler NoRequestNoResponseHandler
get /greet/from
}
`
func TestParser(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
@@ -333,6 +352,21 @@ func TestApiHasNoRequestBody(t *testing.T) {
validate(t, filename)
}
func TestApiRoutes(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(apiRouteTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func validate(t *testing.T, api string) {
dir := "_go"
err := DoGenProject(api, dir, true)

View File

@@ -10,28 +10,20 @@ import (
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
goctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/project"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func getParentPackage(dir string) (string, error) {
p, err := project.Prepare(dir, false)
abs, err := filepath.Abs(dir)
if err != nil {
return "", err
}
if len(p.GoMod.Path) > 0 {
goModePath := filepath.Clean(filepath.Dir(p.GoMod.Path))
absPath, err := filepath.Abs(dir)
if err != nil {
return "", err
}
parent := filepath.Clean(goctlutil.JoinPackages(p.GoMod.Module, absPath[len(goModePath):]))
parent = strings.ReplaceAll(parent, "\\", "/")
return parent, nil
projectCtx, err := ctx.Prepare(abs)
if err != nil {
return "", err
}
return p.Package, nil
return filepath.ToSlash(filepath.Join(projectCtx.Path, strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir))), nil
}
func writeIndent(writer io.Writer, indent int) {

View File

@@ -70,6 +70,12 @@ func (p *serviceEntityParser) parseLine(line string, api *spec.ApiSpec, annos []
ch, _, err := reader.ReadRune()
if err != nil {
if err == io.EOF {
if builder.Len() > 0 {
token := strings.TrimSpace(builder.String())
if len(token) > 0 && token != returnsTag {
fields = append(fields, token)
}
}
break
}
return err

View File

@@ -19,7 +19,7 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/configgen"
"github.com/tal-tech/go-zero/tools/goctl/docker"
model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command"
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/cli"
"github.com/tal-tech/go-zero/tools/goctl/tpl"
"github.com/urfave/cli"
)
@@ -211,7 +211,7 @@ var (
Flags: []cli.Flag{
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [option]",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
},
Action: rpc.RpcNew,
@@ -226,7 +226,7 @@ var (
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [option]",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
},
Action: rpc.RpcTemplate,
@@ -239,17 +239,17 @@ var (
Name: "src, s",
Usage: "the file path of the proto source file",
},
cli.StringFlag{
Name: "dir, d",
Usage: `the target path of the code,default path is "${pwd}". [option]`,
cli.StringSliceFlag{
Name: "proto_path, I",
Usage: `native command of protoc, specify the directory in which to search for imports. [optional]`,
},
cli.StringFlag{
Name: "service, srv",
Usage: `the name of rpc service. [option]`,
Name: "dir, d",
Usage: `the target path of the code`,
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [option]",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
},
Action: rpc.Rpc,
@@ -313,7 +313,7 @@ var (
},
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|underline,default is lower",
Usage: "the file naming style, lower|camel|snake, default is lower",
},
cli.BoolFlag{
Name: "idea",

View File

@@ -40,7 +40,7 @@ func MysqlDDL(ctx *cli.Context) error {
}
switch namingStyle {
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
case "":
namingStyle = gen.NamingLower
default:
@@ -87,7 +87,7 @@ func MyDataSource(ctx *cli.Context) error {
}
switch namingStyle {
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
case "":
namingStyle = gen.NamingLower
default:

View File

@@ -8,30 +8,36 @@ import (
var (
commonMysqlDataTypeMap = map[string]string{
// For consistency, all integer types are converted to int64
"tinyint": "int64",
"smallint": "int64",
"mediumint": "int64",
"int": "int64",
"integer": "int64",
"bigint": "int64",
"float": "float64",
"double": "float64",
"decimal": "float64",
"date": "time.Time",
"time": "string",
"year": "int64",
"datetime": "time.Time",
"timestamp": "time.Time",
// number
"bool": "int64",
"boolean": "int64",
"tinyint": "int64",
"smallint": "int64",
"mediumint": "int64",
"int": "int64",
"integer": "int64",
"bigint": "int64",
"float": "float64",
"double": "float64",
"decimal": "float64",
// date&time
"date": "time.Time",
"datetime": "time.Time",
"timestamp": "time.Time",
"time": "string",
"year": "int64",
// string
"char": "string",
"varchar": "string",
"tinyblob": "string",
"binary": "string",
"varbinary": "string",
"tinytext": "string",
"blob": "string",
"text": "string",
"mediumblob": "string",
"mediumtext": "string",
"longblob": "string",
"longtext": "string",
"enum": "string",
"set": "string",
"json": "string",
}
)

View File

@@ -19,7 +19,7 @@ const (
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
NamingLower = "lower"
NamingCamel = "camel"
NamingUnderline = "underline"
NamingSnake = "snake"
)
type (
@@ -81,7 +81,7 @@ func (g *defaultGenerator) Start(withCache bool) error {
switch g.namingStyle {
case NamingCamel:
name = fmt.Sprintf("%sModel.go", tn.ToCamel())
case NamingUnderline:
case NamingSnake:
name = fmt.Sprintf("%s_model.go", tn.ToSnake())
}
filename := filepath.Join(dirAbs, name)

View File

@@ -1,6 +1,8 @@
package gen
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -8,27 +10,55 @@ import (
)
var (
source = "CREATE TABLE `test_user_info` (\n `id` bigint NOT NULL AUTO_INCREMENT,\n `nanosecond` bigint NOT NULL DEFAULT '0',\n `data` varchar(255) DEFAULT '',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `nanosecond_unique` (`nanosecond`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;"
source = "CREATE TABLE `test_user_info` (\n `id` bigint NOT NULL AUTO_INCREMENT,\n `nanosecond` bigint NOT NULL DEFAULT '0',\n `data` varchar(255) DEFAULT '',\n `content` json DEFAULT NULL,\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `nanosecond_unique` (`nanosecond`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;"
)
func TestCacheModel(t *testing.T) {
logx.Disable()
_ = Clean()
g := NewDefaultGenerator(source, "./testmodel/cache", NamingLower)
dir, _ := filepath.Abs("./testmodel")
cacheDir := filepath.Join(dir, "cache")
noCacheDir := filepath.Join(dir, "nocache")
defer func() {
_ = os.RemoveAll(dir)
}()
g := NewDefaultGenerator(source, cacheDir, NamingLower)
err := g.Start(true)
assert.Nil(t, err)
g = NewDefaultGenerator(source, "./testmodel/nocache", NamingLower)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(cacheDir, "testuserinfomodel.go"))
return err == nil
}())
g = NewDefaultGenerator(source, noCacheDir, NamingLower)
err = g.Start(false)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(noCacheDir, "testuserinfomodel.go"))
return err == nil
}())
}
func TestNamingModel(t *testing.T) {
logx.Disable()
_ = Clean()
g := NewDefaultGenerator(source, "./testmodel/camel", NamingCamel)
dir, _ := filepath.Abs("./testmodel")
camelDir := filepath.Join(dir, "camel")
snakeDir := filepath.Join(dir, "snake")
defer func() {
_ = os.RemoveAll(dir)
}()
g := NewDefaultGenerator(source, camelDir, NamingCamel)
err := g.Start(true)
assert.Nil(t, err)
g = NewDefaultGenerator(source, "./testmodel/snake", NamingUnderline)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(camelDir, "TestUserInfoModel.go"))
return err == nil
}())
g = NewDefaultGenerator(source, snakeDir, NamingSnake)
err = g.Start(true)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(snakeDir, "test_user_info_model.go"))
return err == nil
}())
}

View File

@@ -1,20 +0,0 @@
# Change log
## 2020-10-19
* 增加template
## 2020-09-10
* rpc greet服务一键生成
* 修复相对路径生成rpc服务package引入错误bug
* 移除`--shared`参数
## 2020-08-29
* 新增支持windows生成
## 2020-08-27
* 新增支持rpc模板生成
* 新增支持rpc服务生成

View File

@@ -7,9 +7,8 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
* 简单易用
* 快速提升开发效率
* 出错率低
* 支持基于main proto作为相对路径的import
* 支持map、enum类型
* 支持any类型
* 贴近protoc
## 快速开始
@@ -19,44 +18,41 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
如生成greet rpc服务
```shell script
```Bash
goctl rpc new greet
```
执行后代码结构如下:
```golang
└── greet
├── etc
│   └── greet.yaml
├── go.mod
├── go.sum
── greet
│   ├── greet.go
│   ├── greet_mock.go
│   └── types.go
├── greet.go
├── greet.proto
├── internal
│   ── config
│   │   └── config.go
│   ── logic
│   │   └── pinglogic.go
│   ── server
│   │   └── greetserver.go
│   └── svc
│   └── servicecontext.go
└── pb
└── greet.pb.go
.
├── etc // 配置文件
│   └── greet.yaml
├── go.mod
├── greet // client call
│   └── greet.go
├── greet.go // main entry
├── greet.proto
└── internal
├── config // 配置声明
│   └── config.go
├── greet // pb.go
│   ── greet.pb.go
├── logic // logic
│   ── pinglogic.go
├── server // pb invoker
│   ── greetserver.go
└── svc // resource dependency
└── servicecontext.go
```
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```shell script
```Bash
goctl rpc template -o=user.proto
```
@@ -87,35 +83,10 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
* 生成rpc服务代码
```shell script
```Bash
goctl rpc proto -src=user.proto
```
代码tree
```Plain Text
user
├── etc
│   └── user.json
├── internal
│   ├── config
│   │   └── config.go
│   ├── handler
│   │   ├── loginhandler.go
│   ├── logic
│   │   └── loginlogic.go
│   └── svc
│   └── servicecontext.go
├── pb
│   └── user.pb.go
├── shared
│   ├── mockusermodel.go
│   ├── types.go
│   └── usermodel.go
├── user.go
└── user.proto
```
## 准备工作
* 安装了go环境
@@ -126,11 +97,11 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
### rpc服务生成用法
```shell script
```Bash
goctl rpc proto -h
```
```shell script
```Bash
NAME:
goctl rpc proto - generate rpc from proto
@@ -139,35 +110,22 @@ USAGE:
OPTIONS:
--src value, -s value the file path of the proto source file
--dir value, -d value the target path of the code,default path is "${pwd}". [option]
--service value, --srv value the name of rpc service. [option]
--idea whether the command execution environment is from idea plugin. [option]
--proto_path value, -I value native command of protoc,specify the directory in which to search for imports. [optional]
--dir value, -d value the target path of the code,default path is "${pwd}". [optional]
--idea whether the command execution environment is from idea plugin. [optional]
```
### 参数说明
* --src 必填proto数据源目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖
* --dir 非必填默认为proto文件所在目录生成代码的目标目录
* --service 服务名称非必填默认为proto文件所在目录名称但是如果proto所在目录为一下结构
```shell script
user
├── cmd
│   └── rpc
│   └── user.proto
```
则服务名称亦为user而非proto所在文件夹名称了这里推荐使用这种结构可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
> 注意这里的shared文件夹名称将会是代码中的package名称。
* --idea 非必填是否为idea插件中执行保留字段终端执行可以忽略
* --src 必填proto数据源目前暂时支持单个proto文件生成
* --proto_path 可选protoc原生子命令用于指定proto import从何处查找可指定多个路径,如`goctl rpc -I={path1} -I={path2} ...`,在没有import时可不填。当前proto路径不用指定已经内置`-I`的详细用法请参考`protoc -h`
* --dir 可选默认为proto文件所在目录生成代码的目标目录
* --idea 可选是否为idea插件中执行终端执行可以忽略
### 开发人员需要做什么
关注业务代码编写将重复性、与业务无关的工作交给goctl生成好rpc服务代码后人员仅需要修改
关注业务代码编写将重复性、与业务无关的工作交给goctl生成好rpc服务代码后人员仅需要修改
* 服务中的配置文件编写(etc/xx.json、internal/config/config.go)
* 服务中业务逻辑编写(internal/logic/xxlogic.go)
@@ -193,69 +151,54 @@ OPTIONS:
的标识,请注意不要将也写业务性代码写在里面。
## any和import支持
* 支持any类型声明
* 支持import其他proto文件
## proto import
* 对于rpc中的requestType和returnType必须在main proto文件定义对于proto中的message可以像protoc一样import其他proto文件。
any类型固定import为`google/protobuf/any.proto`,且从${GOPATH}/src中查找proto的import支持main proto的相对路径的import且与proto文件对应的pb.go文件必须在proto目录中能被找到。不支持工程外的其他proto文件import。
proto示例:
> ⚠️注意: 不支持proto嵌套import被import的proto文件不支持import。
### import书写格式
import书写格式
```golang
// @{package_of_pb}
import {proto_omport}
```
@{package_of_pb}pb文件的真实import目录。
{proto_omport}proto import
demo中的
```golang
// @greet/base
import "base/base.proto";
```
工程目录结构如下
```
greet
│   ├── base
│   │   ├── base.pb.go
│   │   └── base.proto
│   ├── demo.proto
│   ├── go.mod
│   └── go.sum
```
demo
```golang
### 错误import
```proto
syntax = "proto3";
import "google/protobuf/any.proto";
// @greet/base
import "base/base.proto";
package stream;
package greet;
enum Gender{
UNKNOWN = 0;
MAN = 1;
WOMAN = 2;
import "base/common.proto"
message Request {
string ping = 1;
}
message StreamResp{
string name = 2;
Gender gender = 3;
google.protobuf.Any details = 5;
base.StreamReq req = 6;
message Response {
string pong = 1;
}
service StreamGreeter {
rpc greet(base.StreamReq) returns (StreamResp);
service Greet {
rpc Ping(base.In) returns(base.Out);// request和return 不支持import
}
```
### 正确import
```proto
syntax = "proto3";
package greet;
import "base/common.proto"
message Request {
base.In in = 1;// 支持import
}
message Response {
base.Out out = 2;// 支持import
}
service Greet {
rpc Ping(Request) returns(Response);
}
```
## 常见问题解决(go mod工程)

View File

@@ -1,108 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: base.proto
package base
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type IdRequest struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *IdRequest) Reset() { *m = IdRequest{} }
func (m *IdRequest) String() string { return proto.CompactTextString(m) }
func (*IdRequest) ProtoMessage() {}
func (*IdRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_db1b6b0986796150, []int{0}
}
func (m *IdRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_IdRequest.Unmarshal(m, b)
}
func (m *IdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_IdRequest.Marshal(b, m, deterministic)
}
func (m *IdRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_IdRequest.Merge(m, src)
}
func (m *IdRequest) XXX_Size() int {
return xxx_messageInfo_IdRequest.Size(m)
}
func (m *IdRequest) XXX_DiscardUnknown() {
xxx_messageInfo_IdRequest.DiscardUnknown(m)
}
var xxx_messageInfo_IdRequest proto.InternalMessageInfo
func (m *IdRequest) GetId() string {
if m != nil {
return m.Id
}
return ""
}
type EmptyResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *EmptyResponse) Reset() { *m = EmptyResponse{} }
func (m *EmptyResponse) String() string { return proto.CompactTextString(m) }
func (*EmptyResponse) ProtoMessage() {}
func (*EmptyResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_db1b6b0986796150, []int{1}
}
func (m *EmptyResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_EmptyResponse.Unmarshal(m, b)
}
func (m *EmptyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_EmptyResponse.Marshal(b, m, deterministic)
}
func (m *EmptyResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_EmptyResponse.Merge(m, src)
}
func (m *EmptyResponse) XXX_Size() int {
return xxx_messageInfo_EmptyResponse.Size(m)
}
func (m *EmptyResponse) XXX_DiscardUnknown() {
xxx_messageInfo_EmptyResponse.DiscardUnknown(m)
}
var xxx_messageInfo_EmptyResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*IdRequest)(nil), "base.IdRequest")
proto.RegisterType((*EmptyResponse)(nil), "base.EmptyResponse")
}
func init() { proto.RegisterFile("base.proto", fileDescriptor_db1b6b0986796150) }
var fileDescriptor_db1b6b0986796150 = []byte{
// 91 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0x4a, 0x2c, 0x4e,
0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xa4, 0xb9, 0x38, 0x3d, 0x53,
0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0xf8, 0xb8, 0x98, 0x32, 0x53, 0x24, 0x18, 0x15,
0x18, 0x35, 0x38, 0x83, 0x98, 0x32, 0x53, 0x94, 0xf8, 0xb9, 0x78, 0x5d, 0x73, 0x0b, 0x4a, 0x2a,
0x83, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x93, 0xd8, 0xc0, 0x5a, 0x8d, 0x01, 0x01, 0x00,
0x00, 0xff, 0xff, 0xe1, 0x39, 0x3c, 0x22, 0x48, 0x00, 0x00, 0x00,
}

View File

@@ -1,11 +0,0 @@
syntax = "proto3";
package base;
message IdRequest {
string id = 1;
}
message EmptyResponse {
}

View File

@@ -0,0 +1,67 @@
package cli
import (
"errors"
"fmt"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/generator"
"github.com/urfave/cli"
)
// Rpc is to generate rpc service code from a proto file by specifying a proto file using flag src,
// you can specify a target folder for code generation, when the proto file has import, you can specify
// the import search directory through the proto_path command, for specific usage, please refer to protoc -h
func Rpc(c *cli.Context) error {
src := c.String("src")
out := c.String("dir")
protoImportPath := c.StringSlice("proto_path")
if len(src) == 0 {
return errors.New("the proto source can not be nil")
}
if len(out) == 0 {
return errors.New("the target directory can not be nil")
}
g := generator.NewDefaultRpcGenerator()
return g.Generate(src, out, protoImportPath)
}
// RpcNew is to generate rpc greet service, this greet service can speed
// up your understanding of the zrpc service structure
func RpcNew(c *cli.Context) error {
name := c.Args().First()
ext := filepath.Ext(name)
if len(ext) > 0 {
return fmt.Errorf("unexpected ext: %s", ext)
}
protoName := name + ".proto"
filename := filepath.Join(".", name, protoName)
src, err := filepath.Abs(filename)
if err != nil {
return err
}
err = generator.ProtoTmpl(src)
if err != nil {
return err
}
workDir := filepath.Dir(src)
_, err = execx.Run("go mod init "+name, workDir)
if err != nil {
return err
}
g := generator.NewDefaultRpcGenerator()
return g.Generate(src, filepath.Dir(src), nil)
}
func RpcTemplate(c *cli.Context) error {
name := c.Args().First()
if len(name) == 0 {
name = "greet.proto"
}
return generator.ProtoTmpl(name)
}

View File

@@ -1,60 +0,0 @@
package command
import (
"fmt"
"os"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/ctx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/gen"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli"
)
func Rpc(c *cli.Context) error {
rpcCtx := ctx.MustCreateRpcContextFromCli(c)
generator := gen.NewDefaultRpcGenerator(rpcCtx)
rpcCtx.Must(generator.Generate())
return nil
}
func RpcTemplate(c *cli.Context) error {
out := c.String("out")
idea := c.Bool("idea")
generator := gen.NewRpcTemplate(out, idea)
generator.MustGenerate(true)
return nil
}
func RpcNew(c *cli.Context) error {
idea := c.Bool("idea")
arg := c.Args().First()
if len(arg) == 0 {
arg = "greet"
}
abs, err := filepath.Abs(arg)
if err != nil {
return err
}
_, err = os.Stat(abs)
if err != nil {
if !os.IsNotExist(err) {
return err
}
err = util.MkdirIfNotExist(abs)
if err != nil {
return err
}
}
dir := filepath.Base(filepath.Clean(abs))
protoSrc := filepath.Join(abs, fmt.Sprintf("%v.proto", dir))
templateGenerator := gen.NewRpcTemplate(protoSrc, idea)
templateGenerator.MustGenerate(false)
rpcCtx := ctx.MustCreateRpcContext(protoSrc, "", "", idea)
generator := gen.NewDefaultRpcGenerator(rpcCtx)
rpcCtx.Must(generator.Generate())
return nil
}

View File

@@ -1,99 +0,0 @@
package ctx
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/project"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/tal-tech/go-zero/tools/goctl/vars"
"github.com/urfave/cli"
)
const (
flagSrc = "src"
flagDir = "dir"
flagService = "service"
flagIdea = "idea"
)
type RpcContext struct {
ProjectPath string
ProjectName stringx.String
ServiceName stringx.String
CurrentPath string
Module string
ProtoFileSrc string
ProtoSource string
TargetDir string
IsInGoEnv bool
console.Console
}
func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
log := console.NewConsole(idea)
if stringx.From(protoSrc).IsEmptyOrSpace() {
log.Fatalln("expected proto source, but nothing found")
}
srcFp, err := filepath.Abs(protoSrc)
log.Must(err)
if !util.FileExists(srcFp) {
log.Fatalln("%s is not exists", srcFp)
}
current := filepath.Dir(srcFp)
if stringx.From(targetDir).IsEmptyOrSpace() {
targetDir = current
}
targetDirFp, err := filepath.Abs(targetDir)
log.Must(err)
if stringx.From(serviceName).IsEmptyOrSpace() {
serviceName = getServiceFromRpcStructure(targetDirFp)
}
serviceNameString := stringx.From(serviceName)
if serviceNameString.IsEmptyOrSpace() {
log.Fatalln("service name not found")
}
info, err := project.Prepare(targetDir, true)
log.Must(err)
return &RpcContext{
ProjectPath: info.Path,
ProjectName: stringx.From(info.Name),
ServiceName: serviceNameString,
CurrentPath: current,
Module: info.GoMod.Module,
ProtoFileSrc: srcFp,
ProtoSource: filepath.Base(srcFp),
TargetDir: targetDirFp,
IsInGoEnv: info.IsInGoEnv,
Console: log,
}
}
func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext {
os := runtime.GOOS
switch os {
case vars.OsMac, vars.OsLinux, vars.OsWindows:
default:
logx.Must(fmt.Errorf("unexpected os: %s", os))
}
protoSrc := ctx.String(flagSrc)
targetDir := ctx.String(flagDir)
serviceName := ctx.String(flagService)
idea := ctx.Bool(flagIdea)
return MustCreateRpcContext(protoSrc, targetDir, serviceName, idea)
}
func getServiceFromRpcStructure(targetDir string) string {
targetDir = filepath.Clean(targetDir)
suffix := filepath.Join("cmd", "rpc")
return filepath.Base(strings.TrimSuffix(targetDir, suffix))
}

View File

@@ -6,7 +6,9 @@ import (
"fmt"
"os/exec"
"runtime"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/vars"
)
@@ -24,17 +26,17 @@ func Run(arg string, dir string) (string, error) {
if len(dir) > 0 {
cmd.Dir = dir
}
dtsout := new(bytes.Buffer)
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd.Stdout = dtsout
cmd.Stdout = stdout
cmd.Stderr = stderr
err := cmd.Run()
if err != nil {
if stderr.Len() > 0 {
return "", errors.New(stderr.String())
return "", errors.New(strings.TrimSuffix(stderr.String(), util.NL))
}
return "", err
}
return dtsout.String(), nil
return strings.TrimSuffix(stdout.String(), util.NL), nil
}

View File

@@ -1,93 +0,0 @@
package gen
import (
"github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/tools/goctl/rpc/ctx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
)
const (
dirTarget = "dirTarget"
dirConfig = "config"
dirEtc = "etc"
dirSvc = "svc"
dirServer = "server"
dirLogic = "logic"
dirPb = "pb"
dirInternal = "internal"
fileConfig = "config.go"
fileServiceContext = "servicecontext.go"
)
type defaultRpcGenerator struct {
dirM map[string]string
Ctx *ctx.RpcContext
ast *parser.PbAst
}
func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator {
return &defaultRpcGenerator{
Ctx: ctx,
}
}
func (g *defaultRpcGenerator) Generate() (err error) {
g.Ctx.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/zero-doc/blob/main/doc/goctl-rpc.md」")
g.Ctx.Warning("-> generating rpc code ...")
defer func() {
if err == nil {
g.Ctx.MarkDone()
}
}()
err = g.createDir()
if err != nil {
return
}
err = g.initGoMod()
if err != nil {
return
}
err = g.genEtc()
if err != nil {
return
}
err = g.genPb()
if err != nil {
return
}
err = g.genConfig()
if err != nil {
return
}
err = g.genSvc()
if err != nil {
return
}
err = g.genLogic()
if err != nil {
return
}
err = g.genHandler()
if err != nil {
return
}
err = g.genMain()
if err != nil {
return
}
err = g.genCall()
if err != nil {
return
}
return
}

View File

@@ -1,224 +0,0 @@
package gen
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const (
typesFilename = "types.go"
callTemplateText = `{{.head}}
//go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE
package {{.filePackage}}
import (
"context"
{{.package}}
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
{{.serviceName}} interface {
{{.interface}}
}
default{{.serviceName}} struct {
cli zrpc.Client
}
)
func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} {
return &default{{.serviceName}}{
cli: cli,
}
}
{{.functions}}
`
callTemplateTypes = `{{.head}}
package {{.filePackage}}
import "errors"
var errJsonConvert = errors.New("json convert error")
{{.const}}
{{.types}}
`
callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
{{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) (*{{.pbResponse}},error)`
callFunctionTemplate = `
{{if .hasComment}}{{.comment}}{{end}}
func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequestName}}) (*{{.pbResponse}}, error) {
var request {{.pbRequest}}
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := {{.package}}.New{{.rpcServiceName}}Client(m.cli.Conn())
resp, err := client.{{.method}}(ctx, &request)
if err != nil{
return nil, err
}
var ret {{.pbResponse}}
bts, err = jsonx.Marshal(resp)
if err != nil{
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &ret)
if err != nil{
return nil, errJsonConvert
}
return &ret, nil
}
`
)
func (g *defaultRpcGenerator) genCall() error {
file := g.ast
if len(file.Service) == 0 {
return nil
}
if len(file.Service) > 1 {
return fmt.Errorf("we recommend only one service in a proto, currently %d", len(file.Service))
}
typeCode, err := file.GenTypesCode()
if err != nil {
return err
}
constLit, err := file.GenEnumCode()
if err != nil {
return err
}
service := file.Service[0]
callPath := filepath.Join(g.dirM[dirTarget], service.Name.Lower())
if err = util.MkdirIfNotExist(callPath); err != nil {
return err
}
filename := filepath.Join(callPath, typesFilename)
head := util.GetHead(g.Ctx.ProtoSource)
text, err := util.LoadTemplate(category, callTypesTemplateFile, callTemplateTypes)
if err != nil {
return err
}
err = util.With("types").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"const": constLit,
"filePackage": service.Name.Lower(),
"serviceName": g.Ctx.ServiceName.Title(),
"lowerStartServiceName": g.Ctx.ServiceName.UnTitle(),
"types": typeCode,
}, filename, true)
if err != nil {
return err
}
filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower()))
functions, importList, err := g.genFunction(service)
if err != nil {
return err
}
iFunctions, err := g.getInterfaceFuncs(service)
if err != nil {
return err
}
text, err = util.LoadTemplate(category, callTemplateFile, callTemplateText)
if err != nil {
return err
}
err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"name": service.Name.Lower(),
"head": head,
"filePackage": service.Name.Lower(),
"package": strings.Join(importList, util.NL),
"serviceName": service.Name.Title(),
"functions": strings.Join(functions, util.NL),
"interface": strings.Join(iFunctions, util.NL),
}, filename, true)
return err
}
func (g *defaultRpcGenerator) genFunction(service *parser.RpcService) ([]string, []string, error) {
file := g.ast
pkgName := file.Package
functions := make([]string, 0)
imports := collection.NewSet()
imports.AddStr(fmt.Sprintf(`%v "%v"`, pkgName, g.mustGetPackage(dirPb)))
for _, method := range service.Funcs {
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
text, err := util.LoadTemplate(category, callFunctionTemplateFile, callFunctionTemplate)
if err != nil {
return nil, nil, err
}
buffer, err := util.With("sharedFn").Parse(text).Execute(map[string]interface{}{
"rpcServiceName": service.Name.Title(),
"method": method.Name.Title(),
"package": pkgName,
"pbRequestName": method.ParameterIn.Name,
"pbRequest": method.ParameterIn.Expression,
"pbResponse": method.ParameterOut.Name,
"hasComment": method.HaveDoc(),
"comment": method.GetDoc(),
})
if err != nil {
return nil, nil, err
}
functions = append(functions, buffer.String())
}
return functions, imports.KeysStr(), nil
}
func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) {
functions := make([]string, 0)
for _, method := range service.Funcs {
text, err := util.LoadTemplate(category, callInterfaceFunctionTemplateFile, callInterfaceFunctionTemplate)
if err != nil {
return nil, err
}
buffer, err := util.With("interfaceFn").Parse(text).Execute(
map[string]interface{}{
"hasComment": method.HaveDoc(),
"comment": method.GetDoc(),
"method": method.Name.Title(),
"pbRequest": method.ParameterIn.Name,
"pbResponse": method.ParameterOut.Name,
})
if err != nil {
return nil, err
}
functions = append(functions, buffer.String())
}
return functions, nil
}

View File

@@ -1,54 +0,0 @@
package gen
import (
"path/filepath"
"runtime"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/vars"
)
// target
// ├── etc
// ├── internal
// │   ├── config
// │   ├── handler
// │   ├── logic
// │   ├── pb
// │   └── svc
func (g *defaultRpcGenerator) createDir() error {
ctx := g.Ctx
m := make(map[string]string)
m[dirTarget] = ctx.TargetDir
m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc)
m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal)
m[dirConfig] = filepath.Join(ctx.TargetDir, dirInternal, dirConfig)
m[dirServer] = filepath.Join(ctx.TargetDir, dirInternal, dirServer)
m[dirLogic] = filepath.Join(ctx.TargetDir, dirInternal, dirLogic)
m[dirPb] = filepath.Join(ctx.TargetDir, dirPb)
m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc)
for _, d := range m {
err := util.MkdirIfNotExist(d)
if err != nil {
return err
}
}
g.dirM = m
return nil
}
func (g *defaultRpcGenerator) mustGetPackage(dir string) string {
target := g.dirM[dir]
projectPath := g.Ctx.ProjectPath
relativePath := strings.TrimPrefix(target, projectPath)
os := runtime.GOOS
switch os {
case vars.OsWindows:
relativePath = filepath.ToSlash(relativePath)
case vars.OsMac, vars.OsLinux:
default:
g.Ctx.Fatalln("unexpected os: %s", os)
}
return g.Ctx.Module + relativePath
}

View File

@@ -1,109 +0,0 @@
package gen
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const (
logicTemplate = `package logic
import (
"context"
{{.imports}}
"github.com/tal-tech/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}}
func (l *{{.logicName}}) {{.method}} (in {{.request}}) ({{.response}}, error) {
// todo: add your logic here and delete this line
return &{{.responseType}}{}, nil
}
`
)
func (g *defaultRpcGenerator) genLogic() error {
logicPath := g.dirM[dirLogic]
protoPkg := g.ast.Package
service := g.ast.Service
for _, item := range service {
for _, method := range item.Funcs {
logicName := fmt.Sprintf("%slogic.go", method.Name.Lower())
filename := filepath.Join(logicPath, logicName)
functions, importList, err := g.genLogicFunction(protoPkg, method)
if err != nil {
return err
}
imports := collection.NewSet()
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
imports.AddStr(svcImport)
imports.AddStr(importList...)
text, err := util.LoadTemplate(category, logicTemplateFileFile, logicTemplate)
if err != nil {
return err
}
err = util.With("logic").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
"functions": functions,
"imports": strings.Join(imports.KeysStr(), util.NL),
}, filename, false)
if err != nil {
return err
}
}
}
return nil
}
func (g *defaultRpcGenerator) genLogicFunction(packageName string, method *parser.Func) (string, []string, error) {
var functions = make([]string, 0)
var imports = collection.NewSet()
if method.ParameterIn.Package == packageName || method.ParameterOut.Package == packageName {
imports.AddStr(fmt.Sprintf(`%v "%v"`, packageName, g.mustGetPackage(dirPb)))
}
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
imports.AddStr(g.ast.Imports[method.ParameterOut.Package])
text, err := util.LoadTemplate(category, logicFuncTemplateFileFile, logicFunctionTemplate)
if err != nil {
return "", nil, err
}
buffer, err := util.With("fun").Parse(text).Execute(map[string]interface{}{
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
"method": method.Name.Title(),
"request": method.ParameterIn.StarExpression,
"response": method.ParameterOut.StarExpression,
"responseType": method.ParameterOut.Expression,
"hasComment": method.HaveDoc(),
"comment": method.GetDoc(),
})
if err != nil {
return "", nil, err
}
functions = append(functions, buffer.String())
return strings.Join(functions, util.NL), imports.KeysStr(), nil
}

View File

@@ -1,85 +0,0 @@
package gen
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const mainTemplate = `{{.head}}
package main
import (
"flag"
"fmt"
{{.imports}}
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
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)
{{.srv}}
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
{{.registers}}
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}
`
func (g *defaultRpcGenerator) genMain() error {
mainPath := g.dirM[dirTarget]
file := g.ast
pkg := file.Package
fileName := filepath.Join(mainPath, fmt.Sprintf("%v.go", g.Ctx.ServiceName.Lower()))
imports := make([]string, 0)
pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirServer))
configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig))
imports = append(imports, configImport, pbImport, remoteImport, svcImport)
srv, registers := g.genServer(pkg, file.Service)
head := util.GetHead(g.Ctx.ProtoSource)
text, err := util.LoadTemplate(category, mainTemplateFile, mainTemplate)
if err != nil {
return err
}
return util.With("main").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"package": pkg,
"serviceName": g.Ctx.ServiceName.Lower(),
"srv": srv,
"registers": registers,
"imports": strings.Join(imports, util.NL),
}, fileName, false)
}
func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (string, string) {
list1 := make([]string, 0)
list2 := make([]string, 0)
for _, item := range list {
name := item.Name.UnTitle()
list1 = append(list1, fmt.Sprintf("%sSrv := server.New%sServer(ctx)", name, item.Name.Title()))
list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name))
}
return strings.Join(list1, util.NL), strings.Join(list2, util.NL)
}

View File

@@ -1,82 +0,0 @@
package gen
import (
"bytes"
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
)
const (
protocCmd = "protoc"
grpcPluginCmd = "--go_out=plugins=grpc"
)
func (g *defaultRpcGenerator) genPb() error {
pbPath := g.dirM[dirPb]
// deprecated: containsAny will be removed in the feature
imports, containsAny, err := parser.ParseImport(g.Ctx.ProtoFileSrc)
if err != nil {
return err
}
err = g.protocGenGo(pbPath, imports)
if err != nil {
return err
}
ast, err := parser.Transfer(g.Ctx.ProtoFileSrc, pbPath, imports, g.Ctx.Console)
if err != nil {
return err
}
ast.ContainsAny = containsAny
if len(ast.Service) == 0 {
return fmt.Errorf("service not found")
}
g.ast = ast
return nil
}
func (g *defaultRpcGenerator) protocGenGo(target string, imports []*parser.Import) error {
dir := filepath.Dir(g.Ctx.ProtoFileSrc)
// cmd join,see the document of proto generating class @https://developers.google.com/protocol-buffers/docs/proto3#generating
// template: protoc -I=${import_path} -I=${other_import_path} -I=${...} --go_out=plugins=grpc,M${pb_package_kv}, M${...} :${target_dir}
// eg: protoc -I=${GOPATH}/src -I=. example.proto --go_out=plugins=grpc,Mbase/base.proto=github.com/go-zero/base.proto:.
// note: the external import out of the project which are found in ${GOPATH}/src so far.
buffer := new(bytes.Buffer)
buffer.WriteString(protocCmd + " ")
targetImportFiltered := collection.NewSet()
for _, item := range imports {
buffer.WriteString(fmt.Sprintf("-I=%s ", item.OriginalDir))
if len(item.BridgeImport) == 0 {
continue
}
targetImportFiltered.AddStr(item.BridgeImport)
}
buffer.WriteString("-I=${GOPATH}/src ")
buffer.WriteString(fmt.Sprintf("-I=%s %s ", dir, g.Ctx.ProtoFileSrc))
buffer.WriteString(grpcPluginCmd)
if targetImportFiltered.Count() > 0 {
buffer.WriteString(fmt.Sprintf(",%v", strings.Join(targetImportFiltered.KeysStr(), ",")))
}
buffer.WriteString(":" + target)
g.Ctx.Debug("-> " + buffer.String())
stdout, err := execx.Run(buffer.String(), "")
if err != nil {
return err
}
if len(stdout) > 0 {
g.Ctx.Info(stdout)
}
return nil
}

View File

@@ -1,116 +0,0 @@
package gen
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const (
serverTemplate = `{{.head}}
package server
import (
"context"
{{.imports}}
)
type {{.types}}
func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server {
return &{{.server}}Server{
svcCtx: svcCtx,
}
}
{{.funcs}}
`
functionTemplate = `
{{if .hasComment}}{{.comment}}{{end}}
func (s *{{.server}}Server) {{.method}} (ctx context.Context, in {{.request}}) ({{.response}}, error) {
l := logic.New{{.logicName}}(ctx,s.svcCtx)
return l.{{.method}}(in)
}
`
typeFmt = `%sServer struct {
svcCtx *svc.ServiceContext
}`
)
func (g *defaultRpcGenerator) genHandler() error {
serverPath := g.dirM[dirServer]
file := g.ast
logicImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirLogic))
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
imports := collection.NewSet()
imports.AddStr(logicImport, svcImport)
head := util.GetHead(g.Ctx.ProtoSource)
for _, service := range file.Service {
filename := fmt.Sprintf("%vserver.go", service.Name.Lower())
serverFile := filepath.Join(serverPath, filename)
funcList, importList, err := g.genFunctions(service)
if err != nil {
return err
}
imports.AddStr(importList...)
text, err := util.LoadTemplate(category, serverTemplateFile, serverTemplate)
if err != nil {
return err
}
err = util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"types": fmt.Sprintf(typeFmt, service.Name.Title()),
"server": service.Name.Title(),
"imports": strings.Join(imports.KeysStr(), util.NL),
"funcs": strings.Join(funcList, util.NL),
}, serverFile, true)
if err != nil {
return err
}
}
return nil
}
func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string, []string, error) {
file := g.ast
pkg := file.Package
var functionList []string
imports := collection.NewSet()
for _, method := range service.Funcs {
if method.ParameterIn.Package == pkg || method.ParameterOut.Package == pkg {
imports.AddStr(fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)))
}
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
imports.AddStr(g.ast.Imports[method.ParameterOut.Package])
text, err := util.LoadTemplate(category, serverFuncTemplateFile, functionTemplate)
if err != nil {
return nil, nil, err
}
buffer, err := util.With("func").Parse(text).Execute(map[string]interface{}{
"server": service.Name.Title(),
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
"method": method.Name.Title(),
"package": pkg,
"request": method.ParameterIn.StarExpression,
"response": method.ParameterOut.StarExpression,
"hasComment": method.HaveDoc(),
"comment": method.GetDoc(),
})
if err != nil {
return nil, nil, err
}
functionList = append(functionList, buffer.String())
}
return functionList, imports.KeysStr(), nil
}

View File

@@ -1,22 +0,0 @@
package gen
import (
"fmt"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
)
func (g *defaultRpcGenerator) initGoMod() error {
if !g.Ctx.IsInGoEnv {
projectDir := g.dirM[dirTarget]
cmd := fmt.Sprintf("go mod init %s", g.Ctx.ProjectName.Source())
output, err := execx.Run(fmt.Sprintf(cmd), projectDir)
if err != nil {
logx.Error(err)
return err
}
g.Ctx.Info(output)
}
return nil
}

View File

@@ -1,35 +0,0 @@
package base
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
)
func TestParseImport(t *testing.T) {
src, _ := filepath.Abs("./test.proto")
base, _ := filepath.Abs("./base.proto")
imports, containsAny, err := parser.ParseImport(src)
assert.Nil(t, err)
assert.Equal(t, true, containsAny)
assert.Equal(t, 1, len(imports))
assert.Equal(t, "github.com/tal-tech/go-zero/tools/goctl/rpc", imports[0].PbImportName)
assert.Equal(t, base, imports[0].OriginalProtoPath)
}
func TestTransfer(t *testing.T) {
src, _ := filepath.Abs("./test.proto")
abs, _ := filepath.Abs("./test")
imports, _, _ := parser.ParseImport(src)
proto, err := parser.Transfer(src, abs, imports, console.NewConsole(false))
assert.Nil(t, err)
assert.Equal(t, 1, len(proto.Service))
assert.Equal(t, "Greeter", proto.Service[0].Name.Source())
assert.Equal(t, 5, len(proto.Structure))
data, ok := proto.Structure["map"]
assert.Equal(t, true, ok)
assert.Equal(t, "M", data.Field[0].Name.Source())
}

View File

@@ -0,0 +1,75 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: common.proto
package common
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type User struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) {
return fileDescriptor_555bd8c177793206, []int{0}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
}
func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_User.Marshal(b, m, deterministic)
}
func (m *User) XXX_Merge(src proto.Message) {
xxx_messageInfo_User.Merge(m, src)
}
func (m *User) XXX_Size() int {
return xxx_messageInfo_User.Size(m)
}
func (m *User) XXX_DiscardUnknown() {
xxx_messageInfo_User.DiscardUnknown(m)
}
var xxx_messageInfo_User proto.InternalMessageInfo
func (m *User) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func init() {
proto.RegisterType((*User)(nil), "common.User")
}
func init() { proto.RegisterFile("common.proto", fileDescriptor_555bd8c177793206) }
var fileDescriptor_555bd8c177793206 = []byte{
// 72 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xce, 0xcf, 0xcd,
0xcd, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0xa4, 0xb8, 0x58,
0x42, 0x8b, 0x53, 0x8b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18,
0x35, 0x38, 0x83, 0xc0, 0xec, 0x24, 0x36, 0xb0, 0x52, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff,
0x2c, 0x6d, 0x58, 0x59, 0x3a, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,7 @@
syntax = "proto3";
package common;
message User {
string name = 1;
}

View File

@@ -0,0 +1,33 @@
package generator
import (
"os/exec"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
)
type defaultGenerator struct {
log console.Console
}
func NewDefaultGenerator() *defaultGenerator {
log := console.NewColorConsole()
return &defaultGenerator{
log: log,
}
}
func (g *defaultGenerator) Prepare() error {
_, err := exec.LookPath("go")
if err != nil {
return err
}
_, err = exec.LookPath("protoc")
if err != nil {
return err
}
_, err = exec.LookPath("protoc-gen-go")
return err
}

View File

@@ -0,0 +1,11 @@
package generator
import (
"strings"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
func formatFilename(filename string) string {
return strings.ToLower(stringx.From(filename).ToCamel())
}

View File

@@ -0,0 +1,98 @@
package generator
import (
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
type RpcGenerator struct {
g Generator
}
func NewDefaultRpcGenerator() *RpcGenerator {
return NewRpcGenerator(NewDefaultGenerator())
}
func NewRpcGenerator(g Generator) *RpcGenerator {
return &RpcGenerator{
g: g,
}
}
func (g *RpcGenerator) Generate(src, target string, protoImportPath []string) error {
abs, err := filepath.Abs(target)
if err != nil {
return err
}
err = util.MkdirIfNotExist(abs)
if err != nil {
return err
}
err = g.g.Prepare()
if err != nil {
return err
}
projectCtx, err := ctx.Prepare(abs)
if err != nil {
return err
}
p := parser.NewDefaultProtoParser()
proto, err := p.Parse(src)
if err != nil {
return err
}
dirCtx, err := mkdir(projectCtx, proto)
if err != nil {
return err
}
err = g.g.GenEtc(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenPb(dirCtx, protoImportPath, proto)
if err != nil {
return err
}
err = g.g.GenConfig(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenSvc(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenLogic(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenServer(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenMain(dirCtx, proto)
if err != nil {
return err
}
err = g.g.GenCall(dirCtx, proto)
console.NewColorConsole().MarkDone()
return err
}

View File

@@ -0,0 +1,104 @@
package generator
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
)
func TestRpcGenerateCaseNilImport(t *testing.T) {
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_stream.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
}
}
func TestRpcGenerateCaseOption(t *testing.T) {
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
}
}
func TestRpcGenerateCaseWordOption(t *testing.T) {
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_word_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
}
}
// test keyword go
func TestRpcGenerateCaseGoOption(t *testing.T) {
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_go_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
}
}
func TestRpcGenerateCaseImport(t *testing.T) {
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_import.proto", abs, []string{"./base"})
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.True(t, func() bool {
return strings.Contains(err.Error(), "package base is not in GOROOT")
}())
}
}

View File

@@ -0,0 +1,154 @@
package generator
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
const (
callTemplateText = `{{.head}}
//go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE
package {{.filePackage}}
import (
"context"
{{.package}}
"github.com/tal-tech/go-zero/zrpc"
)
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,in *{{.pbRequest}}) (*{{.pbResponse}},error)`
callFunctionTemplate = `
{{if .hasComment}}{{.comment}}{{end}}
func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequest}}) (*{{.pbResponse}}, error) {
client := {{.package}}.New{{.rpcServiceName}}Client(m.cli.Conn())
return client.{{.method}}(ctx, in)
}
`
)
func (g *defaultGenerator) GenCall(ctx DirContext, proto parser.Proto) error {
dir := ctx.GetCall()
service := proto.Service
head := util.GetHead(proto.Name)
filename := filepath.Join(dir.Filename, fmt.Sprintf("%s.go", formatFilename(service.Name)))
functions, err := g.genFunction(proto.PbPackage, service)
if err != nil {
return err
}
iFunctions, err := g.getInterfaceFuncs(service)
if err != nil {
return err
}
text, err := util.LoadTemplate(category, callTemplateFile, callTemplateText)
if err != nil {
return err
}
var alias []string
for _, item := range service.RPC {
alias = append(alias, fmt.Sprintf("%s = %s", parser.CamelCase(item.RequestType), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(item.RequestType))))
alias = append(alias, fmt.Sprintf("%s = %s", parser.CamelCase(item.ReturnsType), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(item.ReturnsType))))
}
err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"name": formatFilename(service.Name),
"alias": strings.Join(alias, util.NL),
"head": head,
"filePackage": formatFilename(service.Name),
"package": fmt.Sprintf(`"%s"`, ctx.GetPb().Package),
"serviceName": parser.CamelCase(service.Name),
"functions": strings.Join(functions, util.NL),
"interface": strings.Join(iFunctions, util.NL),
}, filename, true)
return err
}
func (g *defaultGenerator) genFunction(goPackage string, service parser.Service) ([]string, error) {
functions := make([]string, 0)
for _, rpc := range service.RPC {
text, err := util.LoadTemplate(category, callFunctionTemplateFile, callFunctionTemplate)
if err != nil {
return nil, err
}
comment := parser.GetComment(rpc.Doc())
buffer, err := util.With("sharedFn").Parse(text).Execute(map[string]interface{}{
"rpcServiceName": stringx.From(service.Name).Title(),
"method": stringx.From(rpc.Name).Title(),
"package": goPackage,
"pbRequest": parser.CamelCase(rpc.RequestType),
"pbResponse": parser.CamelCase(rpc.ReturnsType),
"hasComment": len(comment) > 0,
"comment": comment,
})
if err != nil {
return nil, err
}
functions = append(functions, buffer.String())
}
return functions, nil
}
func (g *defaultGenerator) getInterfaceFuncs(service parser.Service) ([]string, error) {
functions := make([]string, 0)
for _, rpc := range service.RPC {
text, err := util.LoadTemplate(category, callInterfaceFunctionTemplateFile, callInterfaceFunctionTemplate)
if err != nil {
return nil, err
}
comment := parser.GetComment(rpc.Doc())
buffer, err := util.With("interfaceFn").Parse(text).Execute(
map[string]interface{}{
"hasComment": len(comment) > 0,
"comment": comment,
"method": stringx.From(rpc.Name).Title(),
"pbRequest": parser.CamelCase(rpc.RequestType),
"pbResponse": parser.CamelCase(rpc.ReturnsType),
})
if err != nil {
return nil, err
}
functions = append(functions, buffer.String())
}
return functions, nil
}

View File

@@ -0,0 +1,44 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateCall(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenCall(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -1,10 +1,11 @@
package gen
package generator
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
@@ -17,9 +18,9 @@ type Config struct {
}
`
func (g *defaultRpcGenerator) genConfig() error {
configPath := g.dirM[dirConfig]
fileName := filepath.Join(configPath, fileConfig)
func (g *defaultGenerator) GenConfig(ctx DirContext, _ parser.Proto) error {
dir := ctx.GetConfig()
fileName := filepath.Join(dir.Filename, formatFilename("config")+".go")
if util.FileExists(fileName) {
return nil
}

View File

@@ -0,0 +1,48 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateConfig(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenConfig(dirCtx, proto)
assert.Nil(t, err)
// test file exists
err = g.GenConfig(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,15 @@
package generator
import "github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
type Generator interface {
Prepare() error
GenMain(ctx DirContext, proto parser.Proto) error
GenCall(ctx DirContext, proto parser.Proto) error
GenEtc(ctx DirContext, proto parser.Proto) error
GenConfig(ctx DirContext, proto parser.Proto) error
GenLogic(ctx DirContext, proto parser.Proto) error
GenServer(ctx DirContext, proto parser.Proto) error
GenSvc(ctx DirContext, proto parser.Proto) error
GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto) error
}

View File

@@ -1,9 +1,10 @@
package gen
package generator
import (
"fmt"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
@@ -15,12 +16,10 @@ Etcd:
Key: {{.serviceName}}.rpc
`
func (g *defaultRpcGenerator) genEtc() error {
etdDir := g.dirM[dirEtc]
fileName := filepath.Join(etdDir, fmt.Sprintf("%v.yaml", g.Ctx.ServiceName.Lower()))
if util.FileExists(fileName) {
return nil
}
func (g *defaultGenerator) GenEtc(ctx DirContext, _ parser.Proto) error {
dir := ctx.GetEtc()
serviceNameLower := formatFilename(ctx.GetMain().Base)
fileName := filepath.Join(dir.Filename, fmt.Sprintf("%v.yaml", serviceNameLower))
text, err := util.LoadTemplate(category, etcTemplateFileFile, etcTemplate)
if err != nil {
@@ -28,6 +27,6 @@ func (g *defaultRpcGenerator) genEtc() error {
}
return util.With("etc").Parse(text).SaveTo(map[string]interface{}{
"serviceName": g.Ctx.ServiceName.Lower(),
"serviceName": serviceNameLower,
}, fileName, false)
}

View File

@@ -0,0 +1,45 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateEtc(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenEtc(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,101 @@
package generator
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
const (
logicTemplate = `package logic
import (
"context"
{{.imports}}
"github.com/tal-tech/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}}
func (l *{{.logicName}}) {{.method}} (in {{.request}}) ({{.response}}, error) {
// todo: add your logic here and delete this line
return &{{.responseType}}{}, nil
}
`
)
func (g *defaultGenerator) GenLogic(ctx DirContext, proto parser.Proto) error {
dir := ctx.GetLogic()
for _, rpc := range proto.Service.RPC {
filename := filepath.Join(dir.Filename, formatFilename(rpc.Name+"_logic")+".go")
functions, err := g.genLogicFunction(proto.PbPackage, rpc)
if err != nil {
return err
}
imports := collection.NewSet()
imports.AddStr(fmt.Sprintf(`"%v"`, ctx.GetSvc().Package))
imports.AddStr(fmt.Sprintf(`"%v"`, ctx.GetPb().Package))
text, err := util.LoadTemplate(category, logicTemplateFileFile, logicTemplate)
if err != nil {
return err
}
err = util.With("logic").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"logicName": fmt.Sprintf("%sLogic", stringx.From(rpc.Name).Title()),
"functions": functions,
"imports": strings.Join(imports.KeysStr(), util.NL),
}, filename, false)
if err != nil {
return err
}
}
return nil
}
func (g *defaultGenerator) genLogicFunction(goPackage string, rpc *parser.RPC) (string, error) {
var functions = make([]string, 0)
text, err := util.LoadTemplate(category, logicFuncTemplateFileFile, logicFunctionTemplate)
if err != nil {
return "", err
}
logicName := stringx.From(rpc.Name + "_logic").ToCamel()
comment := parser.GetComment(rpc.Doc())
buffer, err := util.With("fun").Parse(text).Execute(map[string]interface{}{
"logicName": logicName,
"method": parser.CamelCase(rpc.Name),
"request": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.RequestType)),
"response": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.ReturnsType)),
"responseType": fmt.Sprintf("%s.%s", goPackage, parser.CamelCase(rpc.ReturnsType)),
"hasComment": len(comment) > 0,
"comment": comment,
})
if err != nil {
return "", err
}
functions = append(functions, buffer.String())
return strings.Join(functions, util.NL), nil
}

View File

@@ -0,0 +1,44 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateLogic(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenLogic(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,70 @@
package generator
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
const mainTemplate = `{{.head}}
package main
import (
"flag"
"fmt"
{{.imports}}
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
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)
srv := server.New{{.service}}Server(ctx)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
{{.pkg}}.Register{{.service}}Server(grpcServer, srv)
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}
`
func (g *defaultGenerator) GenMain(ctx DirContext, proto parser.Proto) error {
dir := ctx.GetMain()
serviceNameLower := formatFilename(ctx.GetMain().Base)
fileName := filepath.Join(dir.Filename, fmt.Sprintf("%v.go", serviceNameLower))
imports := make([]string, 0)
pbImport := fmt.Sprintf(`"%v"`, ctx.GetPb().Package)
svcImport := fmt.Sprintf(`"%v"`, ctx.GetSvc().Package)
remoteImport := fmt.Sprintf(`"%v"`, ctx.GetServer().Package)
configImport := fmt.Sprintf(`"%v"`, ctx.GetConfig().Package)
imports = append(imports, configImport, pbImport, remoteImport, svcImport)
head := util.GetHead(proto.Name)
text, err := util.LoadTemplate(category, mainTemplateFile, mainTemplate)
if err != nil {
return err
}
return util.With("main").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"serviceName": serviceNameLower,
"imports": strings.Join(imports, util.NL),
"pkg": proto.PbPackage,
"service": parser.CamelCase(proto.Service.Name),
}, fileName, false)
}

View File

@@ -0,0 +1,45 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateMain(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenMain(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,31 @@
package generator
import (
"bytes"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
)
func (g *defaultGenerator) GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto) error {
dir := ctx.GetPb()
cw := new(bytes.Buffer)
base := filepath.Dir(proto.Src)
cw.WriteString("protoc ")
for _, ip := range protoImportPath {
cw.WriteString(" -I=" + ip)
}
cw.WriteString(" -I=" + base)
cw.WriteString(" " + proto.Name)
if strings.Contains(proto.GoPackage, "/") {
cw.WriteString(" --go_out=plugins=grpc:" + ctx.GetInternal().Filename)
} else {
cw.WriteString(" --go_out=plugins=grpc:" + dir.Filename)
}
command := cw.String()
g.log.Debug(command)
_, err := execx.Run(command, "")
return err
}

View File

@@ -0,0 +1,184 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateCaseNilImport(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
//_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
if err := g.Prepare(); err == nil {
targetPb := filepath.Join(dirCtx.GetPb().Filename, "test_stream.pb.go")
err = g.GenPb(dirCtx, nil, proto)
assert.Nil(t, err)
assert.True(t, func() bool {
return util.FileExists(targetPb)
}())
}
}
func TestGenerateCaseImport(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
if err := g.Prepare(); err == nil {
err = g.GenPb(dirCtx, nil, proto)
assert.Nil(t, err)
targetPb := filepath.Join(dirCtx.GetPb().Filename, "test_stream.pb.go")
assert.True(t, func() bool {
return util.FileExists(targetPb)
}())
}
}
func TestGenerateCasePathOption(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_option.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
if err := g.Prepare(); err == nil {
err = g.GenPb(dirCtx, nil, proto)
assert.Nil(t, err)
targetPb := filepath.Join(dirCtx.GetPb().Filename, "test_option.pb.go")
assert.True(t, func() bool {
return util.FileExists(targetPb)
}())
}
}
func TestGenerateCaseWordOption(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_word_option.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
if err := g.Prepare(); err == nil {
err = g.GenPb(dirCtx, nil, proto)
assert.Nil(t, err)
targetPb := filepath.Join(dirCtx.GetPb().Filename, "test_word_option.pb.go")
assert.True(t, func() bool {
return util.FileExists(targetPb)
}())
}
}
// test keyword go
func TestGenerateCaseGoOption(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_go_option.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
if err := g.Prepare(); err == nil {
err = g.GenPb(dirCtx, nil, proto)
assert.Nil(t, err)
targetPb := filepath.Join(dirCtx.GetPb().Filename, "test_go_option.pb.go")
assert.True(t, func() bool {
return util.FileExists(targetPb)
}())
}
}

View File

@@ -0,0 +1,102 @@
package generator
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
const (
serverTemplate = `{{.head}}
package server
import (
"context"
{{.imports}}
)
type {{.server}}Server struct {
svcCtx *svc.ServiceContext
}
func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server {
return &{{.server}}Server{
svcCtx: svcCtx,
}
}
{{.funcs}}
`
functionTemplate = `
{{if .hasComment}}{{.comment}}{{end}}
func (s *{{.server}}Server) {{.method}} (ctx context.Context, in {{.request}}) ({{.response}}, error) {
l := logic.New{{.logicName}}(ctx,s.svcCtx)
return l.{{.method}}(in)
}
`
)
func (g *defaultGenerator) GenServer(ctx DirContext, proto parser.Proto) error {
dir := ctx.GetServer()
logicImport := fmt.Sprintf(`"%v"`, ctx.GetLogic().Package)
svcImport := fmt.Sprintf(`"%v"`, ctx.GetSvc().Package)
pbImport := fmt.Sprintf(`"%v"`, ctx.GetPb().Package)
imports := collection.NewSet()
imports.AddStr(logicImport, svcImport, pbImport)
head := util.GetHead(proto.Name)
service := proto.Service
serverFile := filepath.Join(dir.Filename, formatFilename(service.Name+"_server")+".go")
funcList, err := g.genFunctions(proto.PbPackage, service)
if err != nil {
return err
}
text, err := util.LoadTemplate(category, serverTemplateFile, serverTemplate)
if err != nil {
return err
}
err = util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"server": stringx.From(service.Name).Title(),
"imports": strings.Join(imports.KeysStr(), util.NL),
"funcs": strings.Join(funcList, util.NL),
}, serverFile, true)
return err
}
func (g *defaultGenerator) genFunctions(goPackage string, service parser.Service) ([]string, error) {
var functionList []string
for _, rpc := range service.RPC {
text, err := util.LoadTemplate(category, serverFuncTemplateFile, functionTemplate)
if err != nil {
return nil, err
}
comment := parser.GetComment(rpc.Doc())
buffer, err := util.With("func").Parse(text).Execute(map[string]interface{}{
"server": stringx.From(service.Name).Title(),
"logicName": fmt.Sprintf("%sLogic", stringx.From(rpc.Name).Title()),
"method": parser.CamelCase(rpc.Name),
"request": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.RequestType)),
"response": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.ReturnsType)),
"hasComment": len(comment) > 0,
"comment": comment,
})
if err != nil {
return nil, err
}
functionList = append(functionList, buffer.String())
}
return functionList, nil
}

View File

@@ -0,0 +1,45 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateServer(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenServer(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -1,9 +1,10 @@
package gen
package generator
import (
"fmt"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
@@ -22,15 +23,15 @@ func NewServiceContext(c config.Config) *ServiceContext {
}
`
func (g *defaultRpcGenerator) genSvc() error {
svcPath := g.dirM[dirSvc]
fileName := filepath.Join(svcPath, fileServiceContext)
func (g *defaultGenerator) GenSvc(ctx DirContext, _ parser.Proto) error {
dir := ctx.GetSvc()
fileName := filepath.Join(dir.Filename, formatFilename("service_context")+".go")
text, err := util.LoadTemplate(category, svcTemplateFile, svcTemplate)
if err != nil {
return err
}
return util.With("svc").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"imports": fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig)),
"imports": fmt.Sprintf(`"%v"`, ctx.GetConfig().Package),
}, fileName, false)
}

View File

@@ -0,0 +1,40 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateSvc(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.GenSvc(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,152 @@
package generator
import (
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
const (
wd = "wd"
etc = "etc"
internal = "internal"
config = "config"
logic = "logic"
server = "server"
svc = "svc"
pb = "pb"
call = "call"
)
type (
DirContext interface {
GetCall() Dir
GetEtc() Dir
GetInternal() Dir
GetConfig() Dir
GetLogic() Dir
GetServer() Dir
GetSvc() Dir
GetPb() Dir
GetMain() Dir
}
Dir struct {
Base string
Filename string
Package string
}
defaultDirContext struct {
inner map[string]Dir
}
)
func mkdir(ctx *ctx.ProjectContext, proto parser.Proto) (DirContext, error) {
inner := make(map[string]Dir)
etcDir := filepath.Join(ctx.WorkDir, "etc")
internalDir := filepath.Join(ctx.WorkDir, "internal")
configDir := filepath.Join(internalDir, "config")
logicDir := filepath.Join(internalDir, "logic")
serverDir := filepath.Join(internalDir, "server")
svcDir := filepath.Join(internalDir, "svc")
pbDir := filepath.Join(internalDir, proto.GoPackage)
callDir := filepath.Join(ctx.WorkDir, strings.ToLower(stringx.From(proto.Service.Name).ToCamel()))
inner[wd] = Dir{
Filename: ctx.WorkDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(ctx.WorkDir, ctx.Dir))),
Base: filepath.Base(ctx.WorkDir),
}
inner[etc] = Dir{
Filename: etcDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(etcDir, ctx.Dir))),
Base: filepath.Base(etcDir),
}
inner[internal] = Dir{
Filename: internalDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(internalDir, ctx.Dir))),
Base: filepath.Base(internalDir),
}
inner[config] = Dir{
Filename: configDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(configDir, ctx.Dir))),
Base: filepath.Base(configDir),
}
inner[logic] = Dir{
Filename: logicDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(logicDir, ctx.Dir))),
Base: filepath.Base(logicDir),
}
inner[server] = Dir{
Filename: serverDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(serverDir, ctx.Dir))),
Base: filepath.Base(serverDir),
}
inner[svc] = Dir{
Filename: svcDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(svcDir, ctx.Dir))),
Base: filepath.Base(svcDir),
}
inner[pb] = Dir{
Filename: pbDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(pbDir, ctx.Dir))),
Base: filepath.Base(pbDir),
}
inner[call] = Dir{
Filename: callDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(callDir, ctx.Dir))),
Base: filepath.Base(callDir),
}
for _, v := range inner {
err := util.MkdirIfNotExist(v.Filename)
if err != nil {
return nil, err
}
}
return &defaultDirContext{
inner: inner,
}, nil
}
func (d *defaultDirContext) GetCall() Dir {
return d.inner[call]
}
func (d *defaultDirContext) GetEtc() Dir {
return d.inner[etc]
}
func (d *defaultDirContext) GetInternal() Dir {
return d.inner[internal]
}
func (d *defaultDirContext) GetConfig() Dir {
return d.inner[config]
}
func (d *defaultDirContext) GetLogic() Dir {
return d.inner[logic]
}
func (d *defaultDirContext) GetServer() Dir {
return d.inner[server]
}
func (d *defaultDirContext) GetSvc() Dir {
return d.inner[svc]
}
func (d *defaultDirContext) GetPb() Dir {
return d.inner[pb]
}
func (d *defaultDirContext) GetMain() Dir {
return d.inner[wd]
}
func (d *Dir) Valid() bool {
return len(d.Filename) > 0 && len(d.Package) > 0
}

View File

@@ -0,0 +1,130 @@
package generator
import (
"go/build"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestMkDirInGoPath(t *testing.T) {
dft := build.Default
gp := dft.GOPATH
if len(gp) == 0 {
return
}
projectName := stringx.Rand()
dir := filepath.Join(gp, "src", projectName)
err := util.MkdirIfNotExist(dir)
if err != nil {
return
}
defer func() {
_ = os.RemoveAll(dir)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
internal := filepath.Join(dir, "internal")
assert.True(t, true, func() bool {
return filepath.Join(dir, strings.ToLower(projectName)) == dirCtx.GetCall().Filename && projectName == dirCtx.GetCall().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(dir, "etc") == dirCtx.GetEtc().Filename && filepath.Join(projectName, "etc") == dirCtx.GetEtc().Package
}())
assert.True(t, true, func() bool {
return internal == dirCtx.GetInternal().Filename && filepath.Join(projectName, "internal") == dirCtx.GetInternal().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "config") == dirCtx.GetConfig().Filename && filepath.Join(projectName, "internal", "config") == dirCtx.GetConfig().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "logic") == dirCtx.GetLogic().Filename && filepath.Join(projectName, "internal", "logic") == dirCtx.GetLogic().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "server") == dirCtx.GetServer().Filename && filepath.Join(projectName, "internal", "server") == dirCtx.GetServer().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "svc") == dirCtx.GetSvc().Filename && filepath.Join(projectName, "internal", "svc") == dirCtx.GetSvc().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, strings.ToLower(proto.Service.Name)) == dirCtx.GetPb().Filename && filepath.Join(projectName, "internal", strings.ToLower(proto.Service.Name)) == dirCtx.GetPb().Package
}())
assert.True(t, true, func() bool {
return dir == dirCtx.GetMain().Filename && projectName == dirCtx.GetMain().Package
}())
}
func TestMkDirInGoMod(t *testing.T) {
dft := build.Default
gp := dft.GOPATH
if len(gp) == 0 {
return
}
projectName := stringx.Rand()
dir := filepath.Join(gp, "src", projectName)
err := util.MkdirIfNotExist(dir)
if err != nil {
return
}
_, err = execx.Run("go mod init "+projectName, dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(dir)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
internal := filepath.Join(dir, "internal")
assert.True(t, true, func() bool {
return filepath.Join(dir, strings.ToLower(projectName)) == dirCtx.GetCall().Filename && projectName == dirCtx.GetCall().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(dir, "etc") == dirCtx.GetEtc().Filename && filepath.Join(projectName, "etc") == dirCtx.GetEtc().Package
}())
assert.True(t, true, func() bool {
return internal == dirCtx.GetInternal().Filename && filepath.Join(projectName, "internal") == dirCtx.GetInternal().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "config") == dirCtx.GetConfig().Filename && filepath.Join(projectName, "internal", "config") == dirCtx.GetConfig().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "logic") == dirCtx.GetLogic().Filename && filepath.Join(projectName, "internal", "logic") == dirCtx.GetLogic().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "server") == dirCtx.GetServer().Filename && filepath.Join(projectName, "internal", "server") == dirCtx.GetServer().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, "svc") == dirCtx.GetSvc().Filename && filepath.Join(projectName, "internal", "svc") == dirCtx.GetSvc().Package
}())
assert.True(t, true, func() bool {
return filepath.Join(internal, strings.ToLower(proto.Service.Name)) == dirCtx.GetPb().Filename && filepath.Join(projectName, "internal", strings.ToLower(proto.Service.Name)) == dirCtx.GetPb().Package
}())
assert.True(t, true, func() bool {
return dir == dirCtx.GetMain().Filename && projectName == dirCtx.GetMain().Package
}())
}

View File

@@ -1,12 +1,10 @@
package gen
package generator
import (
"path/filepath"
"strings"
"github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
@@ -27,33 +25,23 @@ service {{.serviceName}} {
}
`
type rpcTemplate struct {
out string
console.Console
}
func NewRpcTemplate(out string, idea bool) *rpcTemplate {
return &rpcTemplate{
out: out,
Console: console.NewConsole(idea),
}
}
func (r *rpcTemplate) MustGenerate(showState bool) {
r.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/zero-doc/blob/main/doc/goctl-rpc.md」")
r.Info("-> generating template...")
protoFilename := filepath.Base(r.out)
func ProtoTmpl(out string) error {
protoFilename := filepath.Base(out)
serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename)))
text, err := util.LoadTemplate(category, rpcTemplateFile, rpcTemplateText)
r.Must(err)
if err != nil {
return err
}
dir := filepath.Dir(out)
err = util.MkdirIfNotExist(dir)
if err != nil {
return err
}
err = util.With("t").Parse(text).SaveTo(map[string]string{
"package": serviceName.UnTitle(),
"serviceName": serviceName.Title(),
}, r.out, false)
r.Must(err)
if showState {
r.Success("Done.")
}
}, out, false)
return err
}

View File

@@ -0,0 +1,21 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProtoTmpl(t *testing.T) {
out, err := filepath.Abs("./test/test.proto")
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(filepath.Dir(out))
}()
err = ProtoTmpl(out)
assert.Nil(t, err)
_, err = os.Stat(out)
assert.Nil(t, err)
}

View File

@@ -1,4 +1,4 @@
package gen
package generator
import (
"fmt"
@@ -10,7 +10,6 @@ import (
const (
category = "rpc"
callTemplateFile = "call.tpl"
callTypesTemplateFile = "call-types.tpl"
callInterfaceFunctionTemplateFile = "call-interface-func.tpl"
callFunctionTemplateFile = "call-func.tpl"
configTemplateFileFile = "config.tpl"
@@ -26,7 +25,6 @@ const (
var templates = map[string]string{
callTemplateFile: callTemplateText,
callTypesTemplateFile: callTemplateTypes,
callInterfaceFunctionTemplateFile: callInterfaceFunctionTemplate,
callFunctionTemplateFile: callFunctionTemplate,
configTemplateFileFile: configTemplate,

View File

@@ -1,4 +1,4 @@
package gen
package generator
import (
"io/ioutil"
@@ -90,3 +90,7 @@ func TestUpdate(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, mainTemplate, string(data))
}
func TestGetCategory(t *testing.T) {
assert.Equal(t, category, GetCategory())
}

View File

@@ -0,0 +1,25 @@
// test proto
syntax = "proto3";
package test;
option go_package = "go";
import "test_base.proto";
message TestMessage{
base.CommonReq req = 1;
}
message TestReq{}
message TestReply{
base.CommonReply reply = 2;
}
enum TestEnum {
unknown = 0;
male = 1;
female = 2;
}
service TestService{
rpc TestRpc (TestReq)returns(TestReply);
}

View File

@@ -0,0 +1,12 @@
// test proto
syntax = "proto3";
package base;
message CommonReq {
string in = 1;
}
message CommonReply {
string out = 1;
}

View File

@@ -0,0 +1,18 @@
// test proto
syntax = "proto3";
package stream;
option go_package="go";
message StreamReq {
string name = 1;
}
message StreamResp {
string greet = 1;
}
service StreamGreeter {
rpc greet(StreamReq) returns (StreamResp);
}

View File

@@ -0,0 +1,18 @@
// test proto
syntax = "proto3";
package greet;
import "base/common.proto";
message In {
string name = 1;
common.User user = 2;
}
message Out {
string greet = 1;
}
service StreamGreeter {
rpc greet(In) returns (Out);
}

View File

@@ -0,0 +1,18 @@
// test proto
syntax = "proto3";
package stream;
option go_package="github.com/tal-tech/go-zero";
message StreamReq {
string name = 1;
}
message StreamResp {
string greet = 1;
}
service StreamGreeter {
rpc greet(StreamReq) returns (StreamResp);
}

View File

@@ -0,0 +1,16 @@
// test proto
syntax = "proto3";
package stream;
message StreamReq {
string name = 1;
}
message StreamResp {
string greet = 1;
}
service StreamGreeter {
rpc greet(StreamReq) returns (StreamResp);
}

View File

@@ -0,0 +1,18 @@
// test proto
syntax = "proto3";
package stream;
option go_package="user";
message StreamReq {
string name = 1;
}
message StreamResp {
string greet = 1;
}
service StreamGreeter {
rpc greet(StreamReq) returns (StreamResp);
}

View File

@@ -0,0 +1,10 @@
package parser
import "github.com/emicklei/proto"
func GetComment(comment *proto.Comment) string {
if comment == nil {
return ""
}
return comment.Message()
}

View File

@@ -0,0 +1,7 @@
package parser
import "github.com/emicklei/proto"
type Import struct {
*proto.Import
}

View File

@@ -0,0 +1,7 @@
package parser
import pr "github.com/emicklei/proto"
type Message struct {
*pr.Message
}

Some files were not shown because too many files have changed in this diff Show More