mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-18 20:28:18 +08:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16bfb1b7be | ||
|
|
ef4d4968d6 | ||
|
|
7b4a5e3ec6 | ||
|
|
e6df21e0d2 | ||
|
|
0a2c2d1eca | ||
|
|
a5fb29a6f0 | ||
|
|
f8da301e57 | ||
|
|
cb9075b737 | ||
|
|
3f389a55c2 | ||
|
|
afbd565d87 | ||
|
|
d629acc2b7 | ||
|
|
f32c6a9b28 | ||
|
|
95aa65efb9 | ||
|
|
3806e66cf1 | ||
|
|
bd430baf52 | ||
|
|
48f4154ea8 | ||
|
|
2599e0d28d | ||
|
|
12327fa07d | ||
|
|
57079bf4a4 | ||
|
|
7f6eceb5a3 | ||
|
|
7d7cb836af | ||
|
|
f87d9d1dda | ||
|
|
856b5aadb1 | ||
|
|
f7d778e0ed | ||
|
|
88333ee77f | ||
|
|
e76f44a35b | ||
|
|
c9ec22d5f4 | ||
|
|
afffc1048b | ||
|
|
d0b76b1d9a | ||
|
|
b004b070d7 | ||
|
|
677d581bd1 | ||
|
|
b776468e69 | ||
|
|
4c9315e984 | ||
|
|
668a7011c4 | ||
|
|
cc07a1d69b | ||
|
|
7f99a3baa8 | ||
|
|
9504418462 | ||
|
|
b144a2335c | ||
|
|
7b9ed7a313 | ||
|
|
3d2e9fcb84 | ||
|
|
2b993424c1 | ||
|
|
5e87b33b23 | ||
|
|
9b7cc43dcb | ||
|
|
000b28cf84 | ||
|
|
9fd16cd278 | ||
|
|
b71429e16b | ||
|
|
a13b48c33e | ||
|
|
033525fea8 | ||
|
|
607fc3297a | ||
|
|
4287877b74 | ||
|
|
2b7545ce11 | ||
|
|
60925c1164 | ||
|
|
1c9e81aa28 | ||
|
|
db7dcaa120 | ||
|
|
099d44054d | ||
|
|
f5f873c6bd | ||
|
|
6dbd3eada9 | ||
|
|
cf2d20a211 | ||
|
|
91bfc093f4 | ||
|
|
cf33aae91d | ||
|
|
c9494c8bc7 |
@@ -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
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ spec:
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://etcd0:2379
|
||||
- http://etcd0.discov:2379
|
||||
- --initial-cluster
|
||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||
- --initial-cluster-state
|
||||
@@ -107,7 +107,7 @@ spec:
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://etcd1:2379
|
||||
- http://etcd1.discov:2379
|
||||
- --initial-cluster
|
||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||
- --initial-cluster-state
|
||||
@@ -179,7 +179,7 @@ spec:
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://etcd2:2379
|
||||
- http://etcd2.discov:2379
|
||||
- --initial-cluster
|
||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||
- --initial-cluster-state
|
||||
@@ -251,7 +251,7 @@ spec:
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://etcd3:2379
|
||||
- http://etcd3.discov:2379
|
||||
- --initial-cluster
|
||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||
- --initial-cluster-state
|
||||
@@ -323,7 +323,7 @@ spec:
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://etcd4:2379
|
||||
- http://etcd4.discov:2379
|
||||
- --initial-cluster
|
||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||
- --initial-cluster-state
|
||||
|
||||
@@ -82,6 +82,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) Wait() {
|
||||
pe.Flush()
|
||||
pe.wgBarrier.Guard(func() {
|
||||
pe.waitGroup.Wait()
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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() {
|
||||
|
||||
13
core/stores/cache/cache_test.go
vendored
13
core/stores/cache/cache_test.go
vendored
@@ -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,
|
||||
|
||||
8
core/stores/cache/cachenode.go
vendored
8
core/stores/cache/cachenode.go
vendored
@@ -175,12 +175,12 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
|
||||
}
|
||||
if fresh {
|
||||
return nil
|
||||
} else {
|
||||
// got the result from previous ongoing query
|
||||
c.stat.IncrementTotal()
|
||||
c.stat.IncrementHit()
|
||||
}
|
||||
|
||||
// got the result from previous ongoing query
|
||||
c.stat.IncrementTotal()
|
||||
c.stat.IncrementHit()
|
||||
|
||||
return jsonx.Unmarshal(val.([]byte), v)
|
||||
}
|
||||
|
||||
|
||||
41
core/stores/cache/cachenode_test.go
vendored
41
core/stores/cache/cachenode_test.go
vendored
@@ -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),
|
||||
|
||||
22
core/stores/cache/util_test.go
vendored
22
core/stores/cache/util_test.go
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
const postgreDriverName = "postgres"
|
||||
const postgresDriverName = "postgres"
|
||||
|
||||
func NewPostgre(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
||||
return sqlx.NewSqlConn(postgreDriverName, datasource, opts...)
|
||||
func NewPostgres(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
||||
return sqlx.NewSqlConn(postgresDriverName, datasource, opts...)
|
||||
}
|
||||
|
||||
28
core/stores/redis/redistest/redistest.go
Normal file
28
core/stores/redis/redistest/redistest.go
Normal 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
|
||||
}
|
||||
@@ -16,11 +16,12 @@ 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/fx"
|
||||
"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 +32,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 +48,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 +71,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 +120,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 +207,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 +250,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 +305,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 +326,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 +384,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 +395,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 +402,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 +412,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 +423,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 +431,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 +441,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 +449,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 +461,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 +473,26 @@ 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 fx.DoWithTimeout(func() error {
|
||||
r.Close()
|
||||
return nil
|
||||
}, time.Second)
|
||||
|
||||
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 +513,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 +526,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 +540,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 +550,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 +625,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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
BIN
doc/images/architecture-en.png
Normal file
BIN
doc/images/architecture-en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
BIN
doc/images/architecture.png
Normal file
BIN
doc/images/architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 333 KiB |
BIN
doc/images/benchmark.png
Normal file
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
BIN
doc/images/go-zero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
doc/images/resilience-en.png
Normal file
BIN
doc/images/resilience-en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
BIN
doc/images/resilience.jpg
Normal file
BIN
doc/images/resilience.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
@@ -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 {
|
||||
|
||||
8
go.mod
8
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/dchest/siphash v1.2.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/emicklei/proto v1.9.0
|
||||
@@ -25,7 +26,7 @@ require (
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
||||
github.com/iancoleman/strcase v0.1.2
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
@@ -40,10 +41,11 @@ require (
|
||||
github.com/pierrec/lz4 v2.5.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.5.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
|
||||
github.com/urfave/cli v1.22.4
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
|
||||
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698
|
||||
@@ -59,7 +61,7 @@ require (
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/h2non/gock.v1 v1.0.15
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
10
go.sum
10
go.sum
@@ -42,6 +42,8 @@ github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQa
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -147,6 +149,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
|
||||
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
@@ -248,6 +252,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
@@ -277,6 +283,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk=
|
||||
@@ -446,6 +454,8 @@ gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
|
||||
20
readme-en.md
20
readme-en.md
@@ -1,3 +1,5 @@
|
||||
<img align="right" width="150px" src="doc/images/go-zero.png">
|
||||
|
||||
# go-zero
|
||||
|
||||
English | [简体中文](readme.md)
|
||||
@@ -23,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://github.com/tal-tech/zero-doc/blob/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
|
||||
|
||||
@@ -74,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:
|
||||
|
||||

|
||||

|
||||
|
||||
## 4. Future development plans of go-zero
|
||||
|
||||
@@ -119,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
|
||||
@@ -164,6 +164,8 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
```shell
|
||||
cd greet
|
||||
go mod init
|
||||
go mod tidy
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
@@ -198,7 +200,7 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
## 7. Benchmark
|
||||
|
||||

|
||||

|
||||
|
||||
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
|
||||
139
readme.md
139
readme.md
@@ -1,3 +1,5 @@
|
||||
<img align="right" width="150px" src="doc/images/go-zero.png">
|
||||
|
||||
# go-zero
|
||||
|
||||
[English](readme-en.md) | 简体中文
|
||||
@@ -8,28 +10,28 @@
|
||||
[](https://github.com/tal-tech/go-zero)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## 0. go-zero介绍
|
||||
## 0. go-zero 介绍
|
||||
|
||||
go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||
|
||||
go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
|
||||
|
||||
使用go-zero的好处:
|
||||
使用 go-zero 的好处:
|
||||
|
||||
* 轻松获得支撑千万日活服务的稳定性
|
||||
* 内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
|
||||
* 微服务治理中间件可无缝集成到其它现有框架使用
|
||||
* 极简的API描述,一键生成各端代码
|
||||
* 极简的 API 描述,一键生成各端代码
|
||||
* 自动校验客户端请求参数合法性
|
||||
* 大量微服务治理和并发工具包
|
||||
|
||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture.png" alt="架构图" width="1500" />
|
||||
<img src="doc/images/architecture.png" alt="架构图" width="1500" />
|
||||
|
||||
## 1. go-zero框架背景
|
||||
## 1. go-zero 框架背景
|
||||
|
||||
18年初,我们决定从`Java+MongoDB`的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||
18 年初,我们决定从 `Java+MongoDB` 的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||
|
||||
* 基于Go语言
|
||||
* 基于 Go 语言
|
||||
* 高效的性能
|
||||
* 简洁的语法
|
||||
* 广泛验证的工程效率
|
||||
@@ -40,7 +42,7 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
||||
* 需要有更快速的问题定位能力
|
||||
* 更便捷的增加新特性
|
||||
|
||||
## 2. go-zero框架设计思考
|
||||
## 2. go-zero 框架设计思考
|
||||
|
||||
对于微服务框架的设计,我们期望保障微服务稳定性的同时,也要特别注重研发效率。所以设计之初,我们就有如下一些准则:
|
||||
|
||||
@@ -53,21 +55,21 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
||||
* 对业务开发友好,封装复杂度
|
||||
* 约束做一件事只有一种方式
|
||||
|
||||
我们经历不到半年时间,彻底完成了从`Java+MongoDB`到`Golang+MySQL`为主的微服务体系迁移,并于18年8月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
||||
我们经历不到半年时间,彻底完成了从 `Java+MongoDB` 到 `Golang+MySQL` 为主的微服务体系迁移,并于 18 年 8 月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
||||
|
||||
## 3. go-zero项目实现和特点
|
||||
## 3. go-zero 项目实现和特点
|
||||
|
||||
go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下主要特点:
|
||||
go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点:
|
||||
|
||||
* 强大的工具支持,尽可能少的代码编写
|
||||
* 极简的接口
|
||||
* 完全兼容net/http
|
||||
* 完全兼容 net/http
|
||||
* 支持中间件,方便扩展
|
||||
* 高性能
|
||||
* 面向故障编程,弹性设计
|
||||
* 内建服务发现、负载均衡
|
||||
* 内建限流、熔断、降载,且自动触发,自动恢复
|
||||
* API参数自动校验
|
||||
* API 参数自动校验
|
||||
* 超时级联控制
|
||||
* 自动缓存控制
|
||||
* 链路跟踪、统计报警等
|
||||
@@ -75,7 +77,9 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
||||
|
||||
如下图,我们从多个层面保障了整体服务的高可用:
|
||||
|
||||

|
||||

|
||||
|
||||
觉得不错的话,别忘 **star** 👏
|
||||
|
||||
## 4. Installation
|
||||
|
||||
@@ -91,88 +95,99 @@ 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工具
|
||||
1. 安装 goctl 工具
|
||||
|
||||
`goctl`读作`go control`,不要读成`go C-T-L`。`goctl`的意思是不要被代码控制,而是要去控制它。其中的`go`不是指`golang`。在设计`goctl`之初,我就希望通过`她`来解放我们的双手👈
|
||||
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 ` 她 ` 来解放我们的双手👈
|
||||
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
确保goctl可执行
|
||||
确保 goctl 可执行
|
||||
|
||||
2. 快速生成api服务
|
||||
2. 快速生成 api 服务
|
||||
|
||||
```shell
|
||||
goctl api new greet
|
||||
cd greet
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
```shell
|
||||
goctl api new greet
|
||||
cd greet
|
||||
go mod init
|
||||
go mod tidy
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求:
|
||||
默认侦听在 8888 端口(可以在配置文件里修改),可以通过 curl 请求:
|
||||
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
```
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
```
|
||||
|
||||
返回如下:
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Date: Sun, 30 Aug 2020 15:32:35 GMT
|
||||
Content-Length: 0
|
||||
```
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 22 Oct 2020 14:03:18 GMT
|
||||
Content-Length: 14
|
||||
|
||||
编写业务代码:
|
||||
{"message":""}
|
||||
```
|
||||
|
||||
* api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等
|
||||
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
|
||||
编写业务代码:
|
||||
|
||||
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
|
||||
* api 文件定义了服务对外暴露的路由,可参考 [api 规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* 可以在 servicecontext.go 里面传递依赖给 logic,比如 mysql, redis 等
|
||||
* 在 api 定义的 get/post/put/delete 等请求对应的 logic 里增加业务处理逻辑
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
goctl api dart -api greet.api -dir greet
|
||||
...
|
||||
```
|
||||
3. 可以根据 api 文件生成前端需要的 Java, TypeScript, Dart, JavaScript 代码
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
goctl api dart -api greet.api -dir greet
|
||||
...
|
||||
```
|
||||
|
||||
## 6. Benchmark
|
||||
|
||||

|
||||

|
||||
|
||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
## 7. 文档
|
||||
|
||||
* API文档 (逐步完善中)
|
||||
* API 文档 (逐步完善中)
|
||||
|
||||
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
||||
|
||||
* awesome系列
|
||||
* 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)
|
||||
* [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)
|
||||
* [快速构建高并发微服务 - 多 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)
|
||||
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
||||
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
||||
* [基于prometheus的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||
* [基于 prometheus 的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||
* [文本序列化和反序列化](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)
|
||||
* [快速构建 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** 群,感谢!
|
||||
|
||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
||||
加我之前有劳点一下 ***star***,一个小小的 ***star*** 是作者们回答海量问题的动力🤝
|
||||
|
||||
<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)
|
||||
|
||||
@@ -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]"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
32
rest/internal/context/params_test.go
Normal file
32
rest/internal/context/params_test.go
Normal 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"))
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -10,42 +9,27 @@ 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)
|
||||
return start(host, port, handler, func(srv *http.Server) error {
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}
|
||||
|
||||
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
if server, err := buildHttpsServer(addr, handler, certFile, keyFile); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return StartServer(server)
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer(srv *http.Server) error {
|
||||
proc.AddWrapUpListener(func() {
|
||||
srv.Shutdown(context.Background())
|
||||
return start(host, port, handler, func(srv *http.Server) error {
|
||||
// certFile and keyFile are set in buildHttpsServer
|
||||
return srv.ListenAndServeTLS(certFile, keyFile)
|
||||
})
|
||||
|
||||
return srv.ListenAndServe()
|
||||
}
|
||||
|
||||
func buildHttpServer(addr string, handler http.Handler) *http.Server {
|
||||
return &http.Server{Addr: addr, Handler: handler}
|
||||
}
|
||||
|
||||
func buildHttpsServer(addr string, handler http.Handler, certFile, keyFile string) (*http.Server, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) error {
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
Handler: handler,
|
||||
}
|
||||
waitForCalled := proc.AddWrapUpListener(func() {
|
||||
server.Shutdown(context.Background())
|
||||
})
|
||||
defer waitForCalled()
|
||||
|
||||
config := tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
return &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
TLSConfig: &config,
|
||||
}, nil
|
||||
return run(server)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
const apiTemplate = `info(
|
||||
title: // TODO: add title
|
||||
desc: // TODO: add description
|
||||
author: {{.gitUser}}
|
||||
email: {{.gitEmail}}
|
||||
author: "{{.gitUser}}"
|
||||
email: "{{.gitEmail}}"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
@@ -28,14 +28,10 @@ type response struct {
|
||||
}
|
||||
|
||||
service {{.serviceName}} {
|
||||
@server(
|
||||
handler: // TODO: set handler name and delete this comment
|
||||
)
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
get /users/id/:userId(request) returns(response)
|
||||
|
||||
@server(
|
||||
handler: // TODO: set handler name and delete this comment
|
||||
)
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
post /users/create(request)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -40,7 +40,7 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
|
||||
defer fp.Close()
|
||||
|
||||
var builder strings.Builder
|
||||
for index, route := range api.Service.Routes {
|
||||
for index, route := range api.Service.Routes() {
|
||||
routeComment, _ := util.GetAnnotationValue(route.Annotations, "doc", "summary")
|
||||
if len(routeComment) == 0 {
|
||||
routeComment = "N/A"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/scanner"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -22,39 +24,78 @@ var (
|
||||
)
|
||||
|
||||
func GoFormatApi(c *cli.Context) error {
|
||||
dir := c.String("dir")
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
printToConsole := c.Bool("p")
|
||||
useStdin := c.Bool("stdin")
|
||||
|
||||
var be errorx.BatchError
|
||||
err := filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||
if strings.HasSuffix(path, ".api") {
|
||||
err := ApiFormat(path, printToConsole)
|
||||
if err != nil {
|
||||
be.Add(util.WrapErr(err, fi.Name()))
|
||||
}
|
||||
if useStdin {
|
||||
if err := ApiFormatByStdin(); err != nil {
|
||||
be.Add(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
be.Add(err)
|
||||
} else {
|
||||
dir := c.String("dir")
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return errors.New(dir + ": No such file or directory")
|
||||
}
|
||||
|
||||
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||
if strings.HasSuffix(path, ".api") {
|
||||
if err := ApiFormatByPath(path); err != nil {
|
||||
be.Add(util.WrapErr(err, fi.Name()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
be.Add(err)
|
||||
}
|
||||
if be.NotNil() {
|
||||
errs := be.Err().Error()
|
||||
fmt.Println(errs)
|
||||
scanner.PrintError(os.Stderr, be.Err())
|
||||
os.Exit(1)
|
||||
}
|
||||
return be.Err()
|
||||
}
|
||||
|
||||
func ApiFormat(path string, printToConsole bool) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
func ApiFormatByStdin() error {
|
||||
data, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := reg.ReplaceAllStringFunc(string(data), func(m string) string {
|
||||
result, err := apiFormat(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Print(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ApiFormatByPath(apiFilePath string) error {
|
||||
data, err := ioutil.ReadFile(apiFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := apiFormat(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiFormat(data string) (string, error) {
|
||||
r := reg.ReplaceAllStringFunc(data, func(m string) string {
|
||||
parts := reg.FindStringSubmatch(m)
|
||||
if len(parts) < 2 {
|
||||
return m
|
||||
@@ -67,30 +108,30 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
|
||||
apiStruct, err := parser.ParseApi(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
info := strings.TrimSpace(apiStruct.Info)
|
||||
if len(apiStruct.Service) == 0 {
|
||||
return nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
|
||||
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.Type)))
|
||||
if err != nil {
|
||||
str := err.Error()
|
||||
lineNumber := strings.Index(str, ":")
|
||||
if lineNumber > 0 {
|
||||
ln, err := strconv.ParseInt(str[:lineNumber], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
pn := 0
|
||||
if len(info) > 0 {
|
||||
pn = countRune(info, '\n') + 1
|
||||
}
|
||||
number := int(ln) + pn + 1
|
||||
return errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
|
||||
return "", errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
|
||||
}
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result string
|
||||
@@ -104,14 +145,28 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
result += strings.TrimSpace(string(fs)) + "\n\n"
|
||||
}
|
||||
if len(strings.TrimSpace(apiStruct.Service)) > 0 {
|
||||
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
|
||||
result += formatService(apiStruct.Service) + "\n\n"
|
||||
}
|
||||
|
||||
if printToConsole {
|
||||
_, err := fmt.Print(result)
|
||||
return err
|
||||
return strings.TrimSpace(result), nil
|
||||
}
|
||||
|
||||
func formatService(str string) string {
|
||||
var builder strings.Builder
|
||||
scanner := bufio.NewScanner(strings.NewReader(str))
|
||||
var tapCount = 0
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == ")" || line == "}" {
|
||||
tapCount -= 1
|
||||
}
|
||||
util.WriteIndent(&builder, tapCount)
|
||||
builder.WriteString(line + "\n")
|
||||
if strings.HasSuffix(line, "(") || strings.HasSuffix(line, "{") {
|
||||
tapCount += 1
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(path, []byte(result), os.ModePerm)
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
||||
|
||||
func countRune(s string, r rune) int {
|
||||
|
||||
47
tools/goctl/api/format/format_test.go
Normal file
47
tools/goctl/api/format/format_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
notFormattedStr = `
|
||||
type Request struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
service A-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
formattedStr = `type Request struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
service A-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}`
|
||||
)
|
||||
|
||||
func TestInlineTypeNotExist(t *testing.T) {
|
||||
r, err := apiFormat(notFormattedStr)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, r, formattedStr)
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -30,7 +28,6 @@ var tmpDir = path.Join(os.TempDir(), "goctl")
|
||||
func GoCommand(c *cli.Context) error {
|
||||
apiFile := c.String("api")
|
||||
dir := c.String("dir")
|
||||
force := c.Bool("force")
|
||||
if len(apiFile) == 0 {
|
||||
return errors.New("missing -api")
|
||||
}
|
||||
@@ -38,10 +35,10 @@ func GoCommand(c *cli.Context) error {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
return DoGenProject(apiFile, dir, force)
|
||||
return DoGenProject(apiFile, dir)
|
||||
}
|
||||
|
||||
func DoGenProject(apiFile, dir string, force bool) error {
|
||||
func DoGenProject(apiFile, dir string) error {
|
||||
p, err := parser.NewParser(apiFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -56,17 +53,16 @@ func DoGenProject(apiFile, dir string, force bool) error {
|
||||
logx.Must(genConfig(dir, api))
|
||||
logx.Must(genMain(dir, api))
|
||||
logx.Must(genServiceContext(dir, api))
|
||||
logx.Must(genTypes(dir, api, force))
|
||||
logx.Must(genTypes(dir, api))
|
||||
logx.Must(genHandlers(dir, api))
|
||||
logx.Must(genRoutes(dir, api, force))
|
||||
logx.Must(genRoutes(dir, api))
|
||||
logx.Must(genLogic(dir, api))
|
||||
createGoModFileIfNeed(dir)
|
||||
|
||||
if err := backupAndSweep(apiFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = apiformat.ApiFormat(apiFile, false); err != nil {
|
||||
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -129,34 +125,3 @@ func sweep() error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func createGoModFileIfNeed(dir string) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, hasGoMod := util.FindGoModPath(dir)
|
||||
if hasGoMod {
|
||||
return
|
||||
}
|
||||
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
moduleName := absDir[len(filepath.Dir(absDir))+1:]
|
||||
cmd := exec.Command("go", "mod", "init", moduleName)
|
||||
cmd.Dir = dir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err = cmd.Run(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
|
||||
fmt.Printf(outStr + "\n" + errStr)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
)
|
||||
|
||||
const testApiTemplate = `
|
||||
@@ -22,23 +23,28 @@ info(
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // }
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + `
|
||||
}
|
||||
|
||||
@server(
|
||||
// C0
|
||||
group: greet/s1
|
||||
)
|
||||
// C1
|
||||
service A-api {
|
||||
@server(
|
||||
// C2
|
||||
@server( // C3
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
|
||||
@server(
|
||||
handler: NoResponseHandler
|
||||
)
|
||||
get /greet/get(Request) returns
|
||||
get /greet/from/:name(Request) returns (Response) // hello
|
||||
|
||||
// C4
|
||||
@handler NoResponseHandler // C5
|
||||
get /greet/get(Request)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -178,6 +184,101 @@ service A-api {
|
||||
}
|
||||
`
|
||||
|
||||
const apiHasNoRequest = `
|
||||
service A-api {
|
||||
@handler GreetHandler
|
||||
post /greet/ping ()
|
||||
}
|
||||
`
|
||||
|
||||
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
|
||||
}
|
||||
`
|
||||
|
||||
const hasCommentApiTest = `
|
||||
type Inline struct {
|
||||
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Inline
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // name in path
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"msg"` + "`" + ` // message
|
||||
}
|
||||
|
||||
service A-api {
|
||||
@doc(helloworld)
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
const hasInlineNoExistTest = `
|
||||
|
||||
type Request struct {
|
||||
Inline
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + ` // message
|
||||
}
|
||||
|
||||
service A-api {
|
||||
@doc(helloworld)
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
const importApi = `
|
||||
type ImportData struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
const hasImportApi = `
|
||||
import "importApi.api"
|
||||
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + ` // message
|
||||
}
|
||||
|
||||
service A-api {
|
||||
@server(
|
||||
handler: GreetHandler
|
||||
)
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||
@@ -191,13 +292,13 @@ func TestParser(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, len(api.Types), 2)
|
||||
assert.Equal(t, len(api.Service.Routes), 2)
|
||||
assert.Equal(t, len(api.Service.Routes()), 2)
|
||||
|
||||
assert.Equal(t, api.Service.Routes[0].Path, "/greet/from/:name")
|
||||
assert.Equal(t, api.Service.Routes[1].Path, "/greet/get")
|
||||
assert.Equal(t, api.Service.Routes()[0].Path, "/greet/from/:name")
|
||||
assert.Equal(t, api.Service.Routes()[1].Path, "/greet/get")
|
||||
|
||||
assert.Equal(t, api.Service.Routes[1].RequestType.Name, "Request")
|
||||
assert.Equal(t, api.Service.Routes[1].ResponseType.Name, "")
|
||||
assert.Equal(t, api.Service.Routes()[1].RequestType.Name, "Request")
|
||||
assert.Equal(t, api.Service.Routes()[1].ResponseType.Name, "")
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
@@ -214,7 +315,7 @@ func TestMultiService(t *testing.T) {
|
||||
api, err := parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, len(api.Service.Routes), 2)
|
||||
assert.Equal(t, len(api.Service.Routes()), 2)
|
||||
assert.Equal(t, len(api.Service.Groups), 2)
|
||||
|
||||
validate(t, filename)
|
||||
@@ -241,10 +342,7 @@ func TestInvalidApiFile(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
_, err = parser.NewParser(filename)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
@@ -260,8 +358,8 @@ func TestAnonymousAnnotation(t *testing.T) {
|
||||
api, err := parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, len(api.Service.Routes), 1)
|
||||
assert.Equal(t, api.Service.Routes[0].Annotations[0].Value, "GreetHandler")
|
||||
assert.Equal(t, len(api.Service.Routes()), 1)
|
||||
assert.Equal(t, api.Service.Routes()[0].Annotations[0].Value, "GreetHandler")
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
@@ -311,9 +409,98 @@ func TestApiHasJwtAndMiddleware(t *testing.T) {
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiHasNoRequestBody(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(apiHasNoRequest), 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 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 TestHasCommentRoutes(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(hasCommentApiTest), 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 TestInlineTypeNotExist(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(hasInlineNoExistTest), 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 TestHasImportApi(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(hasImportApi), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
importApiName := "importApi.api"
|
||||
err = ioutil.WriteFile(importApiName, []byte(importApi), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(importApiName)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
api, err := parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
var hasInline bool
|
||||
for _, ty := range api.Types {
|
||||
if ty.Name == "ImportData" {
|
||||
hasInline = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, hasInline)
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, api string) {
|
||||
dir := "_go"
|
||||
err := DoGenProject(api, dir, true)
|
||||
os.RemoveAll(dir)
|
||||
err := DoGenProject(api, dir)
|
||||
defer os.RemoveAll(dir)
|
||||
assert.Nil(t, err)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
@@ -324,6 +511,9 @@ func validate(t *testing.T, api string) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
_, err = execx.Run("go test ./...", dir)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func validateCode(code string) error {
|
||||
|
||||
@@ -60,8 +60,9 @@ func genConfig(dir string, api *spec.ApiSpec) error {
|
||||
"auth": strings.Join(auths, "\n"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
|
||||
@@ -31,11 +31,11 @@ func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
defer fp.Close()
|
||||
|
||||
service := api.Service
|
||||
host, ok := util.GetAnnotationValue(service.Annotations, "server", "host")
|
||||
host, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "host")
|
||||
if !ok {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
port, ok := util.GetAnnotationValue(service.Annotations, "server", "port")
|
||||
port, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "port")
|
||||
if !ok {
|
||||
port = strconv.Itoa(defaultPort)
|
||||
}
|
||||
@@ -55,6 +55,7 @@ func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
|
||||
@@ -23,14 +23,14 @@ import (
|
||||
|
||||
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.{{.RequestType}}
|
||||
{{if .HasRequest}}var req types.{{.RequestType}}
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.Error(w, err)
|
||||
return
|
||||
}
|
||||
}{{end}}
|
||||
|
||||
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}(req)
|
||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
|
||||
if err != nil {
|
||||
httpx.Error(w, err)
|
||||
} else {
|
||||
@@ -47,6 +47,7 @@ type Handler struct {
|
||||
LogicType string
|
||||
Call string
|
||||
HasResp bool
|
||||
HasRequest bool
|
||||
}
|
||||
|
||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
@@ -71,6 +72,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
HasResp: len(route.ResponseType.Name) > 0,
|
||||
HasRequest: len(route.RequestType.Name) > 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -101,7 +103,7 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
||||
buffer := new(bytes.Buffer)
|
||||
err = template.Must(template.New("handlerTemplate").Parse(text)).Execute(buffer, handleObj)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
|
||||
@@ -72,8 +72,9 @@ func genMain(dir string, api *spec.ApiSpec) error {
|
||||
"serviceName": api.Service.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
|
||||
60
tools/goctl/api/gogen/genmiddleware.go
Normal file
60
tools/goctl/api/gogen/genmiddleware.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
var middlewareImplementCode = `
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
type {{.name}} struct {
|
||||
}
|
||||
|
||||
func New{{.name}}() *{{.name}} {
|
||||
return &{{.name}}{}
|
||||
}
|
||||
|
||||
func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO generate middleware implement function, delete after code implementation
|
||||
|
||||
// Passthrough to next handler if need
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func genMiddleware(dir string, middlewares []string) error {
|
||||
for _, item := range middlewares {
|
||||
filename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "middleware" + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||
t := template.Must(template.New("contextTemplate").Parse(middlewareImplementCode))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"name": strings.Title(name),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package gogen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -52,7 +53,7 @@ type (
|
||||
jwtEnabled bool
|
||||
signatureEnabled bool
|
||||
authName string
|
||||
middleware []string
|
||||
middlewares []string
|
||||
}
|
||||
route struct {
|
||||
method string
|
||||
@@ -61,7 +62,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
func genRoutes(dir string, api *spec.ApiSpec) error {
|
||||
var builder strings.Builder
|
||||
groups, err := getRoutes(api)
|
||||
if err != nil {
|
||||
@@ -92,9 +93,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
}
|
||||
|
||||
var routes string
|
||||
if len(g.middleware) > 0 {
|
||||
if len(g.middlewares) > 0 {
|
||||
gbuilder.WriteString("\n}...,")
|
||||
var params = g.middleware
|
||||
var params = g.middlewares
|
||||
for i := range params {
|
||||
params[i] = "serverCtx." + params[i]
|
||||
}
|
||||
@@ -121,11 +122,7 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
}
|
||||
|
||||
filename := path.Join(dir, handlerDir, routesFilename)
|
||||
if !force {
|
||||
if err := util.RemoveOrQuit(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(filename)
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routesFilename)
|
||||
if err != nil {
|
||||
@@ -143,8 +140,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
"routesAdditions": strings.TrimSpace(builder.String()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
@@ -162,8 +160,7 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
||||
continue
|
||||
}
|
||||
}
|
||||
importSet.AddStr(fmt.Sprintf("%s \"%s\"", folder,
|
||||
util.JoinPackages(parentPkg, handlerDir, folder)))
|
||||
importSet.AddStr(fmt.Sprintf("%s \"%s\"", toPrefix(folder), util.JoinPackages(parentPkg, handlerDir, folder)))
|
||||
}
|
||||
}
|
||||
imports := importSet.KeysStr()
|
||||
@@ -186,11 +183,11 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
|
||||
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
} else {
|
||||
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
}
|
||||
}
|
||||
groupedRoutes.routes = append(groupedRoutes.routes, route{
|
||||
@@ -206,7 +203,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
}
|
||||
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
|
||||
for _, item := range strings.Split(value, ",") {
|
||||
groupedRoutes.middleware = append(groupedRoutes.middleware, item)
|
||||
groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
|
||||
}
|
||||
}
|
||||
routes = append(routes, groupedRoutes)
|
||||
@@ -214,3 +211,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func toPrefix(folder string) string {
|
||||
return strings.ReplaceAll(folder, "/", "")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gogen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
@@ -25,8 +26,12 @@ type ServiceContext struct {
|
||||
}
|
||||
|
||||
func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||
return &ServiceContext{Config: c}
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
{{.middlewareAssignment}}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
@@ -57,25 +62,37 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
|
||||
}
|
||||
|
||||
var middlewareStr string
|
||||
for _, item := range getMiddleware(api) {
|
||||
var middlewareAssignment string
|
||||
var middlewares = getMiddleware(api)
|
||||
err = genMiddleware(dir, middlewares)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range middlewares {
|
||||
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
|
||||
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item, fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
|
||||
}
|
||||
|
||||
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
||||
if len(middlewareStr) > 0 {
|
||||
configImport += fmt.Sprintf("\n\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||
configImport += "\n\t\"" + ctlutil.JoinPackages(parentPkg, middlewareDir) + "\""
|
||||
configImport += fmt.Sprintf("\n\t\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("contextTemplate").Parse(text))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"configImport": configImport,
|
||||
"config": "config.Config",
|
||||
"middleware": middlewareStr,
|
||||
"configImport": configImport,
|
||||
"config": "config.Config",
|
||||
"middleware": middlewareStr,
|
||||
"middlewareAssignment": middlewareAssignment,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -42,18 +43,14 @@ func BuildTypes(types []spec.Type) (string, error) {
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func genTypes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
func genTypes(dir string, api *spec.ApiSpec) error {
|
||||
val, err := BuildTypes(api.Types)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := path.Join(dir, typesDir, typesFile)
|
||||
if !force {
|
||||
if err := util.RemoveOrQuit(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(filename)
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typesFile)
|
||||
if err != nil {
|
||||
@@ -71,8 +68,9 @@ func genTypes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
"containsTime": api.ContainsTime(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
@@ -90,12 +88,6 @@ func convertTypeCase(types []spec.Type, t string) (string, error) {
|
||||
if typ.Name == tp {
|
||||
defTypes = append(defTypes, tp)
|
||||
}
|
||||
|
||||
if len(typ.Annotations) > 0 {
|
||||
if value, ok := apiutil.GetAnnotationValue(typ.Annotations, "serverReplacer", tp); ok {
|
||||
t = strings.ReplaceAll(t, tp, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,38 +10,24 @@ 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
|
||||
}
|
||||
|
||||
return p.Package, nil
|
||||
}
|
||||
|
||||
func writeIndent(writer io.Writer, indent int) {
|
||||
for i := 0; i < indent; i++ {
|
||||
fmt.Fprint(writer, "\t")
|
||||
projectCtx, err := ctx.Prepare(abs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join(projectCtx.Path, strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir))), nil
|
||||
}
|
||||
|
||||
func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
|
||||
writeIndent(writer, indent)
|
||||
util.WriteIndent(writer, indent)
|
||||
var err error
|
||||
if len(comment) > 0 {
|
||||
comment = strings.TrimPrefix(comment, "//")
|
||||
|
||||
@@ -7,6 +7,7 @@ const (
|
||||
contextDir = interval + "svc"
|
||||
handlerDir = interval + "handler"
|
||||
logicDir = interval + "logic"
|
||||
middlewareDir = interval + "middleware"
|
||||
typesDir = interval + typesPacket
|
||||
groupProperty = "group"
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packet
|
||||
`
|
||||
|
||||
func genPacket(dir, packetName string, api *spec.ApiSpec) error {
|
||||
for _, route := range api.Service.Routes {
|
||||
for _, route := range api.Service.Routes() {
|
||||
if err := createWith(dir, api, route, packetName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
const apiTemplate = `
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
@@ -27,9 +27,9 @@ service {{.name}}-api {
|
||||
|
||||
func NewService(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
dirName := "greet"
|
||||
if len(args) > 0 {
|
||||
dirName = args.First()
|
||||
dirName := args.First()
|
||||
if len(dirName) == 0 {
|
||||
dirName = "greet"
|
||||
}
|
||||
|
||||
abs, err := filepath.Abs(dirName)
|
||||
@@ -58,6 +58,6 @@ func NewService(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = gogen.DoGenProject(apiFilePath, abs, true)
|
||||
err = gogen.DoGenProject(apiFilePath, abs)
|
||||
return err
|
||||
}
|
||||
|
||||
219
tools/goctl/api/parser/apifileparser.go
Normal file
219
tools/goctl/api/parser/apifileparser.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenInfo = "info"
|
||||
tokenImport = "import"
|
||||
tokenType = "type"
|
||||
tokenService = "service"
|
||||
tokenServiceAnnotation = "@server"
|
||||
)
|
||||
|
||||
type (
|
||||
ApiStruct struct {
|
||||
Info string
|
||||
Type string
|
||||
Service string
|
||||
Imports string
|
||||
serviceBeginLine int
|
||||
}
|
||||
|
||||
apiFileState interface {
|
||||
process(api *ApiStruct, token string) (apiFileState, error)
|
||||
}
|
||||
|
||||
apiRootState struct {
|
||||
*baseState
|
||||
}
|
||||
|
||||
apiInfoState struct {
|
||||
*baseState
|
||||
}
|
||||
|
||||
apiImportState struct {
|
||||
*baseState
|
||||
}
|
||||
|
||||
apiTypeState struct {
|
||||
*baseState
|
||||
}
|
||||
|
||||
apiServiceState struct {
|
||||
*baseState
|
||||
}
|
||||
)
|
||||
|
||||
func ParseApi(src string) (*ApiStruct, error) {
|
||||
var buffer = new(bytes.Buffer)
|
||||
buffer.WriteString(src)
|
||||
api := new(ApiStruct)
|
||||
var lineNumber = api.serviceBeginLine
|
||||
apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
|
||||
st := apiRootState{&apiFile}
|
||||
for {
|
||||
st, err := st.process(api, "")
|
||||
if err == io.EOF {
|
||||
return api, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
|
||||
}
|
||||
if st == nil {
|
||||
return api, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiRootState) process(api *ApiStruct, token string) (apiFileState, error) {
|
||||
var builder strings.Builder
|
||||
for {
|
||||
ch, err := s.readSkipComment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
|
||||
token := builder.String()
|
||||
token = strings.TrimSpace(token)
|
||||
if len(token) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
builder.Reset()
|
||||
switch token {
|
||||
case tokenInfo:
|
||||
info := apiInfoState{s.baseState}
|
||||
return info.process(api, token+string(ch))
|
||||
case tokenImport:
|
||||
tp := apiImportState{s.baseState}
|
||||
return tp.process(api, token+string(ch))
|
||||
case tokenType:
|
||||
ty := apiTypeState{s.baseState}
|
||||
return ty.process(api, token+string(ch))
|
||||
case tokenService:
|
||||
server := apiServiceState{s.baseState}
|
||||
return server.process(api, token+string(ch))
|
||||
case tokenServiceAnnotation:
|
||||
server := apiServiceState{s.baseState}
|
||||
return server.process(api, token+string(ch))
|
||||
default:
|
||||
if strings.HasPrefix(token, "//") {
|
||||
continue
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
|
||||
}
|
||||
default:
|
||||
builder.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
|
||||
for {
|
||||
line, err := s.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.Info += "\n" + token + line
|
||||
token = ""
|
||||
if strings.TrimSpace(line) == string(rightParenthesis) {
|
||||
return &apiRootState{s.baseState}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
|
||||
line, err := s.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line = token + line
|
||||
if len(strings.Fields(line)) != 2 {
|
||||
return nil, errors.New("import syntax error: " + line)
|
||||
}
|
||||
|
||||
api.Imports += "\n" + line
|
||||
return &apiRootState{s.baseState}, nil
|
||||
}
|
||||
|
||||
func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
|
||||
var blockCount = 0
|
||||
for {
|
||||
line, err := s.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.Type += "\n\n" + token + line
|
||||
token = ""
|
||||
line = strings.TrimSpace(line)
|
||||
line = removeComment(line)
|
||||
if strings.HasSuffix(line, leftBrace) {
|
||||
blockCount++
|
||||
}
|
||||
if strings.HasSuffix(line, string(leftParenthesis)) {
|
||||
blockCount++
|
||||
}
|
||||
if strings.HasSuffix(line, string(rightBrace)) {
|
||||
blockCount--
|
||||
}
|
||||
if strings.HasSuffix(line, string(rightParenthesis)) {
|
||||
blockCount--
|
||||
}
|
||||
|
||||
if blockCount == 0 {
|
||||
return &apiRootState{s.baseState}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
|
||||
var blockCount = 0
|
||||
for {
|
||||
line, err := s.readLineSkipComment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
line = token + line
|
||||
token = ""
|
||||
api.Service += "\n" + line
|
||||
line = strings.TrimSpace(line)
|
||||
line = removeComment(line)
|
||||
if strings.HasSuffix(line, leftBrace) {
|
||||
blockCount++
|
||||
}
|
||||
if strings.HasSuffix(line, string(leftParenthesis)) {
|
||||
blockCount++
|
||||
}
|
||||
if line == string(rightBrace) {
|
||||
blockCount--
|
||||
}
|
||||
if line == string(rightParenthesis) {
|
||||
blockCount--
|
||||
}
|
||||
|
||||
if blockCount == 0 {
|
||||
return &apiRootState{s.baseState}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeComment(line string) string {
|
||||
var commentIdx = strings.Index(line, "//")
|
||||
if commentIdx >= 0 {
|
||||
return line[:commentIdx]
|
||||
}
|
||||
return line
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func (s *baseState) parseProperties() (map[string]string, error) {
|
||||
var st = startState
|
||||
|
||||
for {
|
||||
ch, err := s.read()
|
||||
ch, err := s.readSkipComment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -164,6 +164,60 @@ func (s *baseState) read() (rune, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *baseState) readSkipComment() (rune, error) {
|
||||
ch, err := s.read()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isSlash(ch) {
|
||||
value, err := s.mayReadToEndOfLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if value > 0 {
|
||||
ch = value
|
||||
}
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (s *baseState) mayReadToEndOfLine() (rune, error) {
|
||||
ch, err := s.read()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isSlash(ch) {
|
||||
for {
|
||||
value, err := s.read()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isNewline(value) {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
err = s.unread()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (s *baseState) readLineSkipComment() (string, error) {
|
||||
line, err := s.readLine()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var commentIdx = strings.Index(line, "//")
|
||||
if commentIdx >= 0 {
|
||||
return line[:commentIdx], nil
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func (s *baseState) readLine() (string, error) {
|
||||
line, _, err := s.r.ReadLine()
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity
|
||||
}
|
||||
|
||||
func (s *entity) process() error {
|
||||
line, err := s.state.readLine()
|
||||
line, err := s.state.readLineSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func (s *entity) process() error {
|
||||
var annos []spec.Annotation
|
||||
memberLoop:
|
||||
for {
|
||||
ch, err := s.state.read()
|
||||
ch, err := s.state.readSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,13 +70,13 @@ memberLoop:
|
||||
case ch == at:
|
||||
annotationLoop:
|
||||
for {
|
||||
next, err := s.state.read()
|
||||
next, err := s.state.readSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case isSpace(next):
|
||||
if builder.Len() > 0 {
|
||||
if builder.Len() > 0 && annoName == "" {
|
||||
annoName = builder.String()
|
||||
builder.Reset()
|
||||
}
|
||||
@@ -84,6 +84,7 @@ memberLoop:
|
||||
if builder.Len() == 0 {
|
||||
return errors.New("invalid annotation format")
|
||||
}
|
||||
|
||||
if len(annoName) > 0 {
|
||||
value := builder.String()
|
||||
if value != string(leftParenthesis) {
|
||||
@@ -127,7 +128,7 @@ memberLoop:
|
||||
}
|
||||
|
||||
var line string
|
||||
line, err = s.state.readLine()
|
||||
line, err = s.state.readLineSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package parser
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -34,28 +35,46 @@ func NewParser(filename string) (*Parser, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range strings.Split(apiStruct.Imports, "\n") {
|
||||
ip := strings.TrimSpace(item)
|
||||
if len(ip) > 0 {
|
||||
item := strings.TrimPrefix(item, "import")
|
||||
importLine := strings.TrimSpace(item)
|
||||
if len(importLine) > 0 {
|
||||
item := strings.TrimPrefix(importLine, "import")
|
||||
item = strings.TrimSpace(item)
|
||||
item = strings.TrimPrefix(item, `"`)
|
||||
item = strings.TrimSuffix(item, `"`)
|
||||
var path = item
|
||||
if !util.FileExists(item) {
|
||||
path = filepath.Join(filepath.Dir(apiAbsPath), item)
|
||||
}
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.New("import api file not exist: " + item)
|
||||
}
|
||||
|
||||
importStruct, err := ParseApi(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiStruct.StructBody += "\n" + string(content)
|
||||
|
||||
if len(importStruct.Imports) > 0 {
|
||||
return nil, errors.New("import api should not import another api file recursive")
|
||||
}
|
||||
|
||||
apiStruct.Type += "\n" + importStruct.Type
|
||||
apiStruct.Service += "\n" + importStruct.Service
|
||||
}
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(apiStruct.Service)) == 0 {
|
||||
return nil, errors.New("api has no service defined")
|
||||
}
|
||||
|
||||
var buffer = new(bytes.Buffer)
|
||||
buffer.WriteString(apiStruct.Service)
|
||||
return &Parser{
|
||||
r: bufio.NewReader(buffer),
|
||||
typeDef: apiStruct.StructBody,
|
||||
typeDef: apiStruct.Type,
|
||||
api: apiStruct,
|
||||
}, nil
|
||||
}
|
||||
@@ -67,6 +86,7 @@ func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.Types = types
|
||||
var lineNumber = p.api.serviceBeginLine
|
||||
st := newRootState(p.r, &lineNumber)
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
var annos []spec.Annotation
|
||||
var builder strings.Builder
|
||||
for {
|
||||
ch, err := s.read()
|
||||
ch, err := s.readSkipComment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,6 +33,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
if builder.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
token := builder.String()
|
||||
builder.Reset()
|
||||
return s.processToken(token, annos)
|
||||
@@ -44,10 +45,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
var annoName string
|
||||
annoLoop:
|
||||
for {
|
||||
next, err := s.read()
|
||||
next, err := s.readSkipComment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isSpace(next):
|
||||
if builder.Len() > 0 {
|
||||
@@ -58,6 +60,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
if err := s.unread(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if builder.Len() > 0 {
|
||||
annoName = builder.String()
|
||||
builder.Reset()
|
||||
@@ -66,6 +69,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annos = append(annos, spec.Annotation{
|
||||
Name: annoName,
|
||||
Properties: attrs,
|
||||
@@ -79,9 +83,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
||||
if builder.Len() == 0 {
|
||||
return nil, fmt.Errorf("incorrect %q at the beginning of the line", leftParenthesis)
|
||||
}
|
||||
|
||||
if err := s.unread(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := builder.String()
|
||||
builder.Reset()
|
||||
return s.processToken(token, annos)
|
||||
|
||||
@@ -40,9 +40,7 @@ func (s *serviceState) process(api *spec.ApiSpec) (state, error) {
|
||||
}
|
||||
|
||||
api.Service = spec.Service{
|
||||
Name: name,
|
||||
Annotations: append(api.Service.Annotations, s.annos...),
|
||||
Routes: append(api.Service.Routes, routes...),
|
||||
Name: name,
|
||||
Groups: append(api.Service.Groups, spec.Group{
|
||||
Annotations: s.annos,
|
||||
Routes: routes,
|
||||
@@ -70,6 +68,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
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
type typeState struct {
|
||||
*baseState
|
||||
annos []spec.Annotation
|
||||
}
|
||||
|
||||
func newTypeState(state *baseState, annos []spec.Annotation) state {
|
||||
return &typeState{
|
||||
baseState: state,
|
||||
annos: annos,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *typeState) process(api *spec.ApiSpec) (state, error) {
|
||||
var name string
|
||||
var members []spec.Member
|
||||
parser := &typeEntityParser{
|
||||
acceptName: func(n string) {
|
||||
name = n
|
||||
},
|
||||
acceptMember: func(member spec.Member) {
|
||||
members = append(members, member)
|
||||
},
|
||||
}
|
||||
ent := newEntity(s.baseState, api, parser)
|
||||
if err := ent.process(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api.Types = append(api.Types, spec.Type{
|
||||
Name: name,
|
||||
Annotations: s.annos,
|
||||
Members: members,
|
||||
})
|
||||
|
||||
return newRootState(s.r, s.lineNumber), nil
|
||||
}
|
||||
|
||||
type typeEntityParser struct {
|
||||
acceptName func(name string)
|
||||
acceptMember func(member spec.Member)
|
||||
}
|
||||
|
||||
func (p *typeEntityParser) parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error {
|
||||
index := strings.Index(line, "//")
|
||||
comment := ""
|
||||
if index >= 0 {
|
||||
comment = line[index+2:]
|
||||
line = strings.TrimSpace(line[:index])
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(fields) == 1 {
|
||||
p.acceptMember(spec.Member{
|
||||
Annotations: annos,
|
||||
Name: fields[0],
|
||||
Type: fields[0],
|
||||
IsInline: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
name := fields[0]
|
||||
tp := fields[1]
|
||||
var tag string
|
||||
if len(fields) > 2 {
|
||||
tag = fields[2]
|
||||
} else {
|
||||
tag = fmt.Sprintf("`json:\"%s\"`", util.Untitle(name))
|
||||
}
|
||||
|
||||
p.acceptMember(spec.Member{
|
||||
Annotations: annos,
|
||||
Name: name,
|
||||
Type: tp,
|
||||
Tag: tag,
|
||||
Comment: comment,
|
||||
IsInline: false,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *typeEntityParser) setEntityName(name string) {
|
||||
p.acceptName(name)
|
||||
}
|
||||
@@ -2,22 +2,12 @@ package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
var emptyType spec.Type
|
||||
|
||||
type ApiStruct struct {
|
||||
Info string
|
||||
StructBody string
|
||||
Service string
|
||||
Imports string
|
||||
serviceBeginLine int
|
||||
}
|
||||
|
||||
func GetType(api *spec.ApiSpec, t string) spec.Type {
|
||||
for _, tp := range api.Types {
|
||||
if tp.Name == t {
|
||||
@@ -36,6 +26,10 @@ func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
func isSlash(r rune) bool {
|
||||
return r == '/'
|
||||
}
|
||||
|
||||
func isNewline(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
@@ -69,82 +63,3 @@ func skipSpaces(r *bufio.Reader) error {
|
||||
func unread(r *bufio.Reader) error {
|
||||
return r.UnreadRune()
|
||||
}
|
||||
|
||||
func ParseApi(api string) (*ApiStruct, error) {
|
||||
var result ApiStruct
|
||||
scanner := bufio.NewScanner(strings.NewReader(api))
|
||||
var parseInfo = false
|
||||
var parseImport = false
|
||||
var parseType = false
|
||||
var parseService = false
|
||||
var segment string
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
if line == "info(" {
|
||||
parseInfo = true
|
||||
}
|
||||
if line == ")" && parseInfo {
|
||||
parseInfo = false
|
||||
result.Info = segment + ")"
|
||||
segment = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if isImportBeginLine(line) {
|
||||
parseImport = true
|
||||
}
|
||||
if parseImport && (isTypeBeginLine(line) || isServiceBeginLine(line)) {
|
||||
parseImport = false
|
||||
result.Imports = segment
|
||||
segment = line + "\n"
|
||||
continue
|
||||
}
|
||||
|
||||
if isTypeBeginLine(line) {
|
||||
parseType = true
|
||||
}
|
||||
if isServiceBeginLine(line) {
|
||||
parseService = true
|
||||
if parseType {
|
||||
parseType = false
|
||||
result.StructBody = segment
|
||||
segment = line + "\n"
|
||||
continue
|
||||
}
|
||||
}
|
||||
segment += scanner.Text() + "\n"
|
||||
}
|
||||
|
||||
if !parseService {
|
||||
return nil, errors.New("no service defined")
|
||||
}
|
||||
result.Service = segment
|
||||
result.serviceBeginLine = lineBeginOfService(api)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func isImportBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "import") && strings.HasSuffix(line, ".api")
|
||||
}
|
||||
|
||||
func isTypeBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "type")
|
||||
}
|
||||
|
||||
func isServiceBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "@server(") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
||||
}
|
||||
|
||||
func lineBeginOfService(api string) int {
|
||||
scanner := bufio.NewScanner(strings.NewReader(api))
|
||||
var number = 0
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if isServiceBeginLine(line) {
|
||||
break
|
||||
}
|
||||
number++
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (p *Parser) validateDuplicateProperty(tp spec.Type) (bool, string) {
|
||||
|
||||
func (p *Parser) validateDuplicateRouteHandler(api *spec.ApiSpec) (bool, string) {
|
||||
var names []string
|
||||
for _, r := range api.Service.Routes {
|
||||
for _, r := range api.Service.Routes() {
|
||||
handler, ok := util.GetAnnotationValue(r.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("missing handler annotation for %s", r.Path)
|
||||
|
||||
@@ -27,6 +27,14 @@ type Attribute struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (s Service) Routes() []Route {
|
||||
var result []Route
|
||||
for _, group := range s.Groups {
|
||||
result = append(result, group.Routes...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m Member) IsOptional() bool {
|
||||
var option string
|
||||
|
||||
|
||||
@@ -57,10 +57,8 @@ type (
|
||||
}
|
||||
|
||||
Service struct {
|
||||
Name string
|
||||
Annotations []Annotation
|
||||
Routes []Route
|
||||
Groups []Group
|
||||
Name string
|
||||
Groups []Group
|
||||
}
|
||||
|
||||
Type struct {
|
||||
|
||||
@@ -36,7 +36,7 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
|
||||
defer fp.Close()
|
||||
|
||||
var localTypes []spec.Type
|
||||
for _, route := range api.Service.Routes {
|
||||
for _, route := range api.Service.Routes() {
|
||||
rts := apiutil.GetLocalTypes(api, route)
|
||||
localTypes = append(localTypes, rts...)
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func genTypes(localTypes []spec.Type, inlineType func(string) (*spec.Type, error
|
||||
|
||||
func genApi(api *spec.ApiSpec, localTypes []spec.Type, caller string, prefixForType func(string) string) (string, error) {
|
||||
var builder strings.Builder
|
||||
for _, route := range api.Service.Routes {
|
||||
for _, route := range api.Service.Routes() {
|
||||
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing handler annotation for route %q", route.Path)
|
||||
|
||||
@@ -130,7 +130,7 @@ func GetSharedTypes(api *spec.ApiSpec) []spec.Type {
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, route := range api.Service.Routes {
|
||||
for _, route := range api.Service.Routes() {
|
||||
var rts []spec.Type
|
||||
getTypeRecursive(route.RequestType, types, &rts)
|
||||
getTypeRecursive(route.ResponseType, types, &rts)
|
||||
|
||||
@@ -75,3 +75,9 @@ func ComponentName(api *spec.ApiSpec) string {
|
||||
}
|
||||
return name + "Components"
|
||||
}
|
||||
|
||||
func WriteIndent(writer io.Writer, indent int) {
|
||||
for i := 0; i < indent; i++ {
|
||||
fmt.Fprint(writer, "\t")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +42,14 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.New("abs failed: " + c.String("path"))
|
||||
}
|
||||
|
||||
goModPath, hasFound := util.FindGoModPath(path)
|
||||
if !hasFound {
|
||||
return errors.New("go mod not initial")
|
||||
}
|
||||
|
||||
path = strings.TrimSuffix(path, "/config.go")
|
||||
location := path + "/tmp"
|
||||
location := filepath.Join(path, "tmp")
|
||||
err = os.MkdirAll(location, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,10 +78,12 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
path, err = os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Rename(filepath.Dir(goPath)+"/config.yaml", path+"/config.yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -2,16 +2,113 @@ package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/gen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
etcDir = "etc"
|
||||
yamlEtx = ".yaml"
|
||||
)
|
||||
|
||||
func DockerCommand(c *cli.Context) error {
|
||||
goFile := c.String("go")
|
||||
if len(goFile) == 0 {
|
||||
return errors.New("-go can't be empty")
|
||||
}
|
||||
|
||||
return gen.GenerateDockerfile(goFile, "-f", "etc/config.yaml")
|
||||
cfg, err := findConfig(goFile, etcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return generateDockerfile(goFile, "-f", "etc/"+cfg)
|
||||
}
|
||||
|
||||
func findConfig(file, dir string) (string, error) {
|
||||
var files []string
|
||||
err := filepath.Walk(dir, func(path string, f os.FileInfo, _ error) error {
|
||||
if !f.IsDir() {
|
||||
if filepath.Ext(f.Name()) == yamlEtx {
|
||||
files = append(files, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return "", errors.New("no yaml file")
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(filepath.Base(file), ".go")
|
||||
for _, f := range files {
|
||||
if strings.Index(f, name) == 0 {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
||||
return files[0], nil
|
||||
}
|
||||
|
||||
func generateDockerfile(goFile string, args ...string) error {
|
||||
projPath, err := getFilePath(filepath.Dir(goFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pos := strings.IndexByte(projPath, '/')
|
||||
if pos >= 0 {
|
||||
projPath = projPath[pos+1:]
|
||||
}
|
||||
|
||||
out, err := util.CreateIfNotExist("Dockerfile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
text, err := ctlutil.LoadTemplate(category, dockerTemplateFile, dockerTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
for _, arg := range args {
|
||||
builder.WriteString(`, "` + arg + `"`)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("dockerfile").Parse(text))
|
||||
return t.Execute(out, map[string]string{
|
||||
"goRelPath": projPath,
|
||||
"goFile": goFile,
|
||||
"exeFile": util.FileNameWithoutExt(filepath.Base(goFile)),
|
||||
"argument": builder.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func getFilePath(file string) (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
projPath, ok := util.FindGoModPath(filepath.Join(wd, file))
|
||||
if !ok {
|
||||
projPath, err = util.PathFromGoSrc()
|
||||
if err != nil {
|
||||
return "", errors.New("no go.mod found, or not in GOPATH")
|
||||
}
|
||||
}
|
||||
|
||||
return projPath, nil
|
||||
}
|
||||
|
||||
44
tools/goctl/docker/template.go
Normal file
44
tools/goctl/docker/template.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
category = "docker"
|
||||
dockerTemplateFile = "docker.tpl"
|
||||
dockerTemplate = `FROM golang:alpine AS builder
|
||||
|
||||
LABEL stage=gobuilder
|
||||
|
||||
ENV CGO_ENABLED 0
|
||||
ENV GOOS linux
|
||||
ENV GOPROXY https://goproxy.cn,direct
|
||||
|
||||
WORKDIR /build/zero
|
||||
COPY . .
|
||||
COPY {{.goRelPath}}/etc /app/etc
|
||||
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
|
||||
|
||||
|
||||
FROM alpine
|
||||
|
||||
RUN apk update --no-cache
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
|
||||
COPY --from=builder /app/etc /app/etc
|
||||
|
||||
CMD ["./{{.exeFile}}"{{.argument}}]
|
||||
`
|
||||
)
|
||||
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
return util.InitTemplates(category, map[string]string{
|
||||
dockerTemplateFile: dockerTemplate,
|
||||
})
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
func GenerateDockerfile(goFile string, args ...string) error {
|
||||
projPath, err := getFilePath(filepath.Dir(goFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pos := strings.IndexByte(projPath, '/')
|
||||
if pos >= 0 {
|
||||
projPath = projPath[pos+1:]
|
||||
}
|
||||
|
||||
out, err := util.CreateIfNotExist("Dockerfile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
var builder strings.Builder
|
||||
for _, arg := range args {
|
||||
builder.WriteString(`, "` + arg + `"`)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("dockerfile").Parse(dockerTemplate))
|
||||
return t.Execute(out, map[string]string{
|
||||
"projectName": vars.ProjectName,
|
||||
"goRelPath": projPath,
|
||||
"goFile": goFile,
|
||||
"exeFile": util.FileNameWithoutExt(goFile),
|
||||
"argument": builder.String(),
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func getFilePath(file string) (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
projPath, ok := util.FindGoModPath(filepath.Join(wd, file))
|
||||
if !ok {
|
||||
projPath, err = util.PathFromGoSrc()
|
||||
if err != nil {
|
||||
return "", errors.New("no go.mod found, or not in GOPATH")
|
||||
}
|
||||
}
|
||||
|
||||
return projPath, nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package gen
|
||||
|
||||
const dockerTemplate = `FROM golang:alpine AS builder
|
||||
|
||||
LABEL stage=gobuilder
|
||||
|
||||
ENV CGO_ENABLED 0
|
||||
ENV GOOS linux
|
||||
ENV GOPROXY https://goproxy.cn,direct
|
||||
|
||||
WORKDIR $GOPATH/src/{{.projectName}}
|
||||
COPY . .
|
||||
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
|
||||
|
||||
|
||||
FROM alpine
|
||||
|
||||
RUN apk update --no-cache
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
|
||||
|
||||
CMD ["./{{.exeFile}}"{{.argument}}]
|
||||
`
|
||||
@@ -19,13 +19,13 @@ 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"
|
||||
)
|
||||
|
||||
var (
|
||||
BuildVersion = "20201021"
|
||||
BuildVersion = "20201108"
|
||||
commands = []cli.Command{
|
||||
{
|
||||
Name: "api",
|
||||
@@ -51,15 +51,16 @@ var (
|
||||
Name: "dir",
|
||||
Usage: "the format target dir",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "p",
|
||||
Usage: "print result to console",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "iu",
|
||||
Usage: "ignore update",
|
||||
Required: false,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "stdin",
|
||||
Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Action: format.GoFormatApi,
|
||||
},
|
||||
@@ -97,10 +98,6 @@ var (
|
||||
Name: "api",
|
||||
Usage: "the api file",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "force override the exist files",
|
||||
},
|
||||
},
|
||||
Action: gogen.GoCommand,
|
||||
},
|
||||
@@ -187,16 +184,12 @@ var (
|
||||
},
|
||||
{
|
||||
Name: "docker",
|
||||
Usage: "generate Dockerfile and Makefile",
|
||||
Usage: "generate Dockerfile",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "go",
|
||||
Usage: "the file that contains main function",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "namespace, n",
|
||||
Usage: "which namespace of kubernetes to deploy the service",
|
||||
},
|
||||
},
|
||||
Action: docker.DockerCommand,
|
||||
},
|
||||
@@ -210,7 +203,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,
|
||||
@@ -223,10 +216,6 @@ var (
|
||||
Name: "out, o",
|
||||
Usage: "the target path of proto",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [option]",
|
||||
},
|
||||
},
|
||||
Action: rpc.RpcTemplate,
|
||||
},
|
||||
@@ -238,17 +227,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,
|
||||
@@ -269,12 +258,16 @@ var (
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "src, s",
|
||||
Usage: "the file path of the ddl source file",
|
||||
Usage: "the path or path globbing patterns of the ddl",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dir, d",
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
Usage: "generate code with cache [optional]",
|
||||
@@ -296,7 +289,7 @@ var (
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "table, t",
|
||||
Usage: `source table,tables separated by commas,like "user,course`,
|
||||
Usage: `the table or table globbing patterns in the database`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
@@ -306,6 +299,10 @@ var (
|
||||
Name: "dir, d",
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|snake, default is lower",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "for idea plugin [optional]",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package k8s
|
||||
|
||||
var apiRpcTmeplate = `apiVersion: apps/v1beta2
|
||||
var apiRpcTmeplate = `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{.name}}
|
||||
|
||||
@@ -7,7 +7,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
* 通过ddl生成
|
||||
|
||||
```shell script
|
||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
||||
goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c=true
|
||||
```
|
||||
|
||||
执行上述命令后即可快速生成CURD代码。
|
||||
@@ -21,12 +21,13 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
* 通过datasource生成
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./model"
|
||||
```
|
||||
|
||||
* 生成代码示例
|
||||
|
||||
``` go
|
||||
```go
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
@@ -48,9 +49,9 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
|
||||
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
|
||||
|
||||
cacheUserMobilePrefix = "cache#User#mobile#"
|
||||
cacheUserIdPrefix = "cache#User#id#"
|
||||
cacheUserNamePrefix = "cache#User#name#"
|
||||
cacheUserMobilePrefix = "cache#User#mobile#"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -71,23 +72,28 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
}
|
||||
)
|
||||
|
||||
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel {
|
||||
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) *UserModel {
|
||||
return &UserModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: table,
|
||||
table: "user",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) Insert(data User) (sql.Result, error) {
|
||||
query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)`
|
||||
return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
|
||||
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
|
||||
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
|
||||
}, userNameKey, userMobileKey)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOne(id int64) (*User, error) {
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||
var resp User
|
||||
err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
query := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
|
||||
return conn.QueryRow(v, query, id)
|
||||
})
|
||||
switch err {
|
||||
@@ -103,18 +109,13 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
func (m *UserModel) FindOneByName(name string) (*User, error) {
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1`
|
||||
err := m.QueryRowIndex(&resp, userNameKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where name = ? limit 1", userRows, m.table)
|
||||
if err := conn.QueryRow(&resp, query, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
return conn.QueryRow(v, query, primary)
|
||||
})
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
@@ -128,18 +129,13 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1`
|
||||
err := m.QueryRowIndex(&resp, userMobileKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where mobile = ? limit 1", userRows, m.table)
|
||||
if err := conn.QueryRow(&resp, query, mobile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
|
||||
return conn.QueryRow(v, query, primary)
|
||||
})
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
@@ -153,7 +149,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
func (m *UserModel) Update(data User) error {
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?`
|
||||
query := fmt.Sprintf("update %s set %s where id = ?", m.table, userRowsWithPlaceHolder)
|
||||
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
|
||||
}, userIdKey)
|
||||
return err
|
||||
@@ -164,16 +160,26 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
|
||||
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := `delete from ` + m.table + ` where id = ?`
|
||||
query := fmt.Sprintf("delete from %s where id = ?", m.table)
|
||||
return conn.Exec(query, id)
|
||||
}, userIdKey, userNameKey, userMobileKey)
|
||||
}, userMobileKey, userIdKey, userNameKey)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
func (m *UserModel) formatPrimary(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}
|
||||
|
||||
func (m *UserModel) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
|
||||
return conn.QueryRow(v, query, primary)
|
||||
}
|
||||
```
|
||||
|
||||
## 用法
|
||||
|
||||
@@ -205,15 +211,56 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model mysql -src={filename} -dir={dir} -cache=true
|
||||
goctl model mysql -src={patterns} -dir={dir} -cache=true
|
||||
```
|
||||
|
||||
help
|
||||
|
||||
```
|
||||
NAME:
|
||||
goctl model mysql ddl - generate mysql model from ddl
|
||||
|
||||
USAGE:
|
||||
goctl model mysql ddl [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--src value, -s value the path or path globbing patterns of the ddl
|
||||
--dir value, -d value the target dir
|
||||
--style value the file naming style, lower|camel|underline,default is lower
|
||||
--cache, -c generate code with cache [optional]
|
||||
--idea for idea plugin [optional]
|
||||
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=true
|
||||
```
|
||||
|
||||
help
|
||||
|
||||
```
|
||||
NAME:
|
||||
goctl model mysql datasource - generate model from datasource
|
||||
|
||||
USAGE:
|
||||
goctl model mysql datasource [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--url value the data source of database,like "root:password@tcp(127.0.0.1:3306)/database
|
||||
--table value, -t value the table or table globbing patterns in the database
|
||||
--cache, -c generate code with cache [optional]
|
||||
--dir value, -d value the target dir
|
||||
--style value the file naming style, lower|camel|snake, default is lower
|
||||
--idea for idea plugin [optional]
|
||||
|
||||
```
|
||||
|
||||
示例用法请参考[用法](./example/generator.sh)
|
||||
|
||||
> NOTE: goctl model mysql ddl/datasource 均新增了一个`--style`参数,用于标记文件命名风格。
|
||||
|
||||
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
||||
|
||||
* 不带缓存模式
|
||||
@@ -221,26 +268,26 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model -src={filename} -dir={dir}
|
||||
goctl model -src={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
or
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model -src={filename} -dir={dir} -cache=false
|
||||
goctl model -src={patterns} -dir={dir} -cache=false
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=false
|
||||
```
|
||||
|
||||
生成代码仅基本的CURD结构。
|
||||
@@ -265,8 +312,3 @@ OPTIONS:
|
||||
|
||||
目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
|
||||
|
||||
## QA
|
||||
|
||||
* goctl model除了命令行模式,支持插件模式吗?
|
||||
|
||||
很快支持idea插件。
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -19,6 +22,7 @@ const (
|
||||
flagDir = "dir"
|
||||
flagCache = "cache"
|
||||
flagIdea = "idea"
|
||||
flagStyle = "style"
|
||||
flagUrl = "url"
|
||||
flagTable = "table"
|
||||
)
|
||||
@@ -28,17 +32,35 @@ func MysqlDDL(ctx *cli.Context) error {
|
||||
dir := ctx.String(flagDir)
|
||||
cache := ctx.Bool(flagCache)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||
log := console.NewConsole(idea)
|
||||
fileSrc, err := filepath.Abs(src)
|
||||
src = strings.TrimSpace(src)
|
||||
if len(src) == 0 {
|
||||
return errors.New("expected path or path globbing patterns, but nothing found")
|
||||
}
|
||||
|
||||
switch namingStyle {
|
||||
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
files, err := util.MatchFiles(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := ioutil.ReadFile(fileSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var source []string
|
||||
for _, file := range files {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source = append(source, string(data))
|
||||
}
|
||||
source := string(data)
|
||||
generator := gen.NewDefaultGenerator(source, dir, gen.WithConsoleOption(log))
|
||||
generator := gen.NewDefaultGenerator(strings.Join(source, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
|
||||
err = generator.Start(cache)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
@@ -51,36 +73,72 @@ func MyDataSource(ctx *cli.Context) error {
|
||||
dir := strings.TrimSpace(ctx.String(flagDir))
|
||||
cache := ctx.Bool(flagCache)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
table := strings.TrimSpace(ctx.String(flagTable))
|
||||
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||
pattern := strings.TrimSpace(ctx.String(flagTable))
|
||||
log := console.NewConsole(idea)
|
||||
if len(url) == 0 {
|
||||
log.Error("%v", "expected data source of mysql, but is empty")
|
||||
log.Error("%v", "expected data source of mysql, but nothing found")
|
||||
return nil
|
||||
}
|
||||
if len(table) == 0 {
|
||||
log.Error("%v", "expected table(s), but nothing found")
|
||||
|
||||
if len(pattern) == 0 {
|
||||
log.Error("%v", "expected table or table globbing patterns, but nothing found")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch namingStyle {
|
||||
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
cfg, err := mysql.ParseDSN(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Disable()
|
||||
conn := sqlx.NewMysql(url)
|
||||
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
|
||||
db := sqlx.NewMysql(databaseSource)
|
||||
m := model.NewDDLModel(conn)
|
||||
tables := collection.NewSet()
|
||||
for _, item := range strings.Split(table, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if len(item) == 0 {
|
||||
im := model.NewInformationSchemaModel(db)
|
||||
|
||||
tables, err := im.GetAllTables(cfg.DBName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var matchTables []string
|
||||
for _, item := range tables {
|
||||
match, err := filepath.Match(pattern, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
tables.AddStr(item)
|
||||
|
||||
matchTables = append(matchTables, item)
|
||||
}
|
||||
ddl, err := m.ShowDDL(tables.KeysStr()...)
|
||||
if len(matchTables) == 0 {
|
||||
return errors.New("no tables matched")
|
||||
}
|
||||
|
||||
ddl, err := m.ShowDDL(matchTables...)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
return nil
|
||||
}
|
||||
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, gen.WithConsoleOption(log))
|
||||
|
||||
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
|
||||
err = generator.Start(cache)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# generate model with cache from ddl
|
||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c
|
||||
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
|
||||
|
||||
# generate model with cache from data source
|
||||
#goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
#user=root
|
||||
#password=password
|
||||
#datasource=127.0.0.1:3306
|
||||
#database=test
|
||||
#goctl model mysql datasource -url="${user}:${password}@tcp(${datasource})/${database}" -table="*" -dir ./model
|
||||
15
tools/goctl/model/sql/example/sql/user_1.sql
Normal file
15
tools/goctl/model/sql/example/sql/user_1.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 用户表 --
|
||||
CREATE TABLE `user1` (
|
||||
`id` bigint(10) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
|
||||
`password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
|
||||
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
`gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
|
||||
`nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
|
||||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name_index` (`name`),
|
||||
UNIQUE KEY `mobile_index` (`mobile`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
@@ -17,6 +17,9 @@ import (
|
||||
const (
|
||||
pwd = "."
|
||||
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
|
||||
NamingLower = "lower"
|
||||
NamingCamel = "camel"
|
||||
NamingSnake = "snake"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,15 +27,17 @@ type (
|
||||
source string
|
||||
dir string
|
||||
console.Console
|
||||
pkg string
|
||||
namingStyle string
|
||||
}
|
||||
Option func(generator *defaultGenerator)
|
||||
)
|
||||
|
||||
func NewDefaultGenerator(source, dir string, opt ...Option) *defaultGenerator {
|
||||
func NewDefaultGenerator(source, dir, namingStyle string, opt ...Option) *defaultGenerator {
|
||||
if dir == "" {
|
||||
dir = pwd
|
||||
}
|
||||
generator := &defaultGenerator{source: source, dir: dir}
|
||||
generator := &defaultGenerator{source: source, dir: dir, namingStyle: namingStyle}
|
||||
var optionList []Option
|
||||
optionList = append(optionList, newDefaultOption())
|
||||
optionList = append(optionList, opt...)
|
||||
@@ -59,6 +64,8 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.dir = dirAbs
|
||||
g.pkg = filepath.Base(dirAbs)
|
||||
err = util.MkdirIfNotExist(dirAbs)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -69,7 +76,14 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
|
||||
for tableName, code := range modelList {
|
||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).ToCamel()))
|
||||
tn := stringx.From(tableName)
|
||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(tn.ToCamel()))
|
||||
switch g.namingStyle {
|
||||
case NamingCamel:
|
||||
name = fmt.Sprintf("%sModel.go", tn.ToCamel())
|
||||
case NamingSnake:
|
||||
name = fmt.Sprintf("%s_model.go", tn.ToSnake())
|
||||
}
|
||||
filename := filepath.Join(dirAbs, name)
|
||||
if util.FileExists(filename) {
|
||||
g.Warning("%s already exists, ignored.", name)
|
||||
@@ -82,12 +96,18 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
// generate error file
|
||||
filename := filepath.Join(dirAbs, "vars.go")
|
||||
if !util.FileExists(filename) {
|
||||
err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text, err := util.LoadTemplate(category, errTemplateFile, template.Error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.With("vars").Parse(text).SaveTo(map[string]interface{}{
|
||||
"pkg": g.pkg,
|
||||
}, filename, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Success("Done.")
|
||||
return nil
|
||||
}
|
||||
@@ -119,8 +139,12 @@ type (
|
||||
)
|
||||
|
||||
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
|
||||
text, err := util.LoadTemplate(category, modelTemplateFile, template.Model)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t := util.With("model").
|
||||
Parse(template.Model).
|
||||
Parse(text).
|
||||
GoFmt(true)
|
||||
|
||||
m, err := genCacheKeys(in)
|
||||
@@ -188,6 +212,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
||||
}
|
||||
|
||||
output, err := t.Execute(map[string]interface{}{
|
||||
"pkg": g.pkg,
|
||||
"imports": importsCode,
|
||||
"vars": varsCode,
|
||||
"types": typesCode,
|
||||
|
||||
64
tools/goctl/model/sql/gen/gen_test.go
Normal file
64
tools/goctl/model/sql/gen/gen_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
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 `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()
|
||||
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)
|
||||
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()
|
||||
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)
|
||||
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
|
||||
}())
|
||||
}
|
||||
@@ -14,6 +14,7 @@ func genNew(table Table, withCache bool) (string, error) {
|
||||
output, err := util.With("new").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"table": table.Name.Source(),
|
||||
"withCache": withCache,
|
||||
"upperStartCamelObject": table.Name.ToCamel(),
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
typesTemplateFile = "types.tpl"
|
||||
updateTemplateFile = "update.tpl"
|
||||
varTemplateFile = "var.tpl"
|
||||
errTemplateFile = "err.tpl"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
@@ -41,6 +42,7 @@ var templates = map[string]string{
|
||||
typesTemplateFile: template.Types,
|
||||
updateTemplateFile: template.Update,
|
||||
varTemplateFile: template.Vars,
|
||||
errTemplateFile: template.Error,
|
||||
}
|
||||
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
|
||||
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type (
|
||||
InformationSchemaModel struct {
|
||||
conn sqlx.SqlConn
|
||||
}
|
||||
)
|
||||
|
||||
func NewInformationSchemaModel(conn sqlx.SqlConn) *InformationSchemaModel {
|
||||
return &InformationSchemaModel{conn: conn}
|
||||
}
|
||||
|
||||
func (m *InformationSchemaModel) GetAllTables(database string) ([]string, error) {
|
||||
query := `select TABLE_NAME from TABLES where TABLE_SCHEMA = ?`
|
||||
var tables []string
|
||||
err := m.conn.QueryRows(&tables, query, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
var (
|
||||
unSupportDDL = errors.New("unexpected type")
|
||||
tableBodyIsNotFound = errors.New("create table spec not found")
|
||||
errPrimaryKey = errors.New("unexpected joint primary key")
|
||||
errPrimaryKey = errors.New("unexpected join primary key")
|
||||
)
|
||||
|
||||
@@ -9,9 +9,9 @@ func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}}
|
||||
|
||||
{{.keys}}
|
||||
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||
return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
|
||||
}, {{.keyValues}}){{else}}query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
}, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||
_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package template
|
||||
|
||||
var Error = `package model
|
||||
var Error = `package {{.pkg}}
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
|
||||
})
|
||||
switch err {
|
||||
@@ -16,7 +16,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}{{else}}query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
||||
}{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
|
||||
switch err {
|
||||
@@ -36,7 +36,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
||||
return nil, err
|
||||
}
|
||||
}{{else}}var resp {{.upperStartCamelObject}}
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
|
||||
switch err {
|
||||
case nil:
|
||||
@@ -69,7 +69,7 @@ func (m *{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) str
|
||||
}
|
||||
|
||||
func (m *{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryField}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||
return conn.QueryRow(v, query, primary)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
`
|
||||
ImportsNoCache = `import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
{{if .time}}"time"{{end}}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ var Insert = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
||||
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
|
||||
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
return conn.Exec(query, {{.expressionValues}})
|
||||
}, {{.keyValues}}){{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
|
||||
{{end}}{{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||
return ret,err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package template
|
||||
|
||||
var Model = `package model
|
||||
var Model = `package {{.pkg}}
|
||||
{{.imports}}
|
||||
{{.vars}}
|
||||
{{.types}}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package template
|
||||
|
||||
var New = `
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn,{{if .withCache}} c cache.CacheConf,{{end}} table string) *{{.upperStartCamelObject}}Model {
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *{{.upperStartCamelObject}}Model {
|
||||
return &{{.upperStartCamelObject}}Model{
|
||||
{{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}},
|
||||
table: table,
|
||||
table: "{{.table}}",
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user