Compare commits

...

60 Commits

Author SHA1 Message Date
kingxt
e0afe0b4bb optimize api new (#216) 2020-11-19 16:48:48 +08:00
Keson
24fb29a356 patch model&rpc (#207)
* change column to read from information_schema

* reactor generate mode from datasource

* reactor generate mode from datasource

* add primary key check logic

* resolve rebase conflicts

* add naming style

* add filename test case

* resolve rebase conflicts

* reactor test

* add test case

* change shell script to makefile

* update rpc new

* update gen_test.go

* format code

* format code

* update test

* generates alias
2020-11-18 15:32:53 +08:00
kevin
71083b5e64 update readme 2020-11-17 19:01:14 +08:00
kingxt
1174f17bd9 modify image url (#213) 2020-11-17 18:50:22 +08:00
kingxt
d6d8fc21d8 type should not define nested (#212)
* nest type should not supported

* nest type should not supported

* nest type should not supported

* nest type should not supported

* new test

* new test
2020-11-17 18:08:55 +08:00
kevin
9592639cb4 add error handle tests 2020-11-17 18:04:48 +08:00
kevin
abcb28e506 support error customization 2020-11-17 17:11:06 +08:00
kingxt
a92f65580c support type def without struct token (#210)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* optimized

* optimized

* optimized

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-17 15:25:13 +08:00
bittoy
3819f67cf4 add redis geospatial (#209)
* add redis geospatial

* fix go test error
2020-11-16 19:45:43 +08:00
kevin
295c8d2934 fix issue #205 2020-11-16 19:23:24 +08:00
kingxt
88da8685dd optimize parser (#206)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* optimized parser

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-16 10:08:28 +08:00
kevin
c7831ac96d update goctl readme 2020-11-15 21:18:02 +08:00
kevin
e898761762 update example 2020-11-15 21:15:29 +08:00
kevin
13d1c5cd00 update example 2020-11-14 22:01:35 +08:00
kingxt
16bfb1b7be refactor parser and remove deprecated code (#204)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-13 23:01:19 +08:00
kingxt
ef4d4968d6 1. group support multi level folder 2. remove force flag (#203)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 19:47:32 +08:00
kingxt
7b4a5e3ec6 api support for comment double slash // (#201)
* add comment support

* add comment support

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 16:57:28 +08:00
kevin
e6df21e0d2 format code 2020-11-11 17:20:56 +08:00
SunJun
0a2c2d1eca change grpc interceptor to chain interceptor (#200)
* change grpc interceptor to chain interceptor

* change server rpc interceptors, del testing code
2020-11-11 17:15:22 +08:00
kevin
a5fb29a6f0 update etcd yaml to avoid no such nost resolve problem 2020-11-11 11:06:23 +08:00
zhoushuguang
f8da301e57 no default metric (#199)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-10 11:47:08 +08:00
kevin
cb9075b737 add dockerfile into template 2020-11-09 18:02:16 +08:00
kingxt
3f389a55c2 format service and add test (#197)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 17:41:07 +08:00
kevin
afbd565d87 rename postgres 2020-11-09 17:22:51 +08:00
zhoushuguang
d629acc2b7 default metric host (#196)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-09 16:03:07 +08:00
kingxt
f32c6a9b28 rewrite (#194)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 10:06:45 +08:00
kevin
95aa65efb9 add dockerfile generator 2020-11-08 21:28:58 +08:00
kevin
3806e66cf1 simplify http server starter 2020-11-08 13:17:14 +08:00
kevin
bd430baf52 graceful shutdown refined 2020-11-08 13:08:00 +08:00
Keson
48f4154ea8 update doc (#193) 2020-11-08 13:02:48 +08:00
super_mario
2599e0d28d Close the process when shutdown is finished (#157)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2020-11-08 12:50:58 +08:00
kingxt
12327fa07d break generator when happen error (#192)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 21:25:52 +08:00
kevin
57079bf4a4 update cli package 2020-11-07 20:01:25 +08:00
kingxt
7f6eceb5a3 add more test (#189)
* new test

* import bug when with quotation

* new test

* add test condition

* rpc template command use -o param

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 17:13:40 +08:00
kevin
7d7cb836af fix issue #186 2020-11-06 12:25:48 +08:00
kevin
f87d9d1dda refine code style 2020-11-06 12:13:28 +08:00
Keson
856b5aadb1 rpc generation fix (#184)
* reactor alert

* optimize

* add test case

* update the target directory in case proto contains option

* fix missing comments and format code
2020-11-05 19:08:34 +08:00
Keson
f7d778e0ed fix duplicate alias (#183) 2020-11-05 18:12:23 +08:00
kevin
88333ee77f faster the tests 2020-11-05 16:04:00 +08:00
Keson
e76f44a35b reactor rpc (#179)
* reactor rpc generation

* update flag

* update command

* update command

* update unit test

* delete test file

* optimize code

* update doc

* update gen pb

* rename target dir

* update mysql data type convert rule

* add done flag

* optimize req/reply parameter

* optimize req/reply parameter

* remove waste code

* remove duplicate parameter

* format code

* format code

* optimize naming

* reactor rpcv2 to rpc

* remove new line

* format code

* rename underline to snake

* reactor getParentPackage

* remove debug log

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

* fix slash when run on windows

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

View File

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

View File

@@ -204,6 +204,7 @@ func (tw *TimingWheel) removeTask(key interface{}) {
timer := val.(*positionEntry)
timer.item.removed = true
tw.timers.Del(key)
}
func (tw *TimingWheel) run() {
@@ -248,7 +249,6 @@ func (tw *TimingWheel) scanAndRunTasks(l *list.List) {
if task.removed {
next := e.Next()
l.Remove(e)
tw.timers.Del(task.key)
e = next
continue
} else if task.circle > 0 {
@@ -301,6 +301,7 @@ func (tw *TimingWheel) setTask(task *timingEntry) {
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
if val, ok := tw.timers.Get(task.key); ok {
timer := val.(*positionEntry)
timer.item = task
timer.pos = pos
} else {
tw.timers.Set(task.key, &positionEntry{

View File

@@ -594,6 +594,31 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
}
}
func TestMoveAndRemoveTask(t *testing.T) {
ticker := timex.NewFakeTicker()
tick := func(v int) {
for i := 0; i < v; i++ {
ticker.Tick()
}
}
var keys []int
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
assert.Equal(t, "any", k)
assert.Equal(t, 3, v.(int))
keys = append(keys, v.(int))
ticker.Done()
}, ticker)
defer tw.Stop()
tw.SetTimer("any", 3, testStep*8)
tick(6)
tw.MoveTimer("any", testStep*7)
tick(3)
tw.RemoveTimer("any")
tick(30)
time.Sleep(time.Millisecond)
assert.Equal(t, 0, len(keys))
}
func BenchmarkTimingWheel(b *testing.B) {
b.ReportAllocs()

View File

@@ -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

View File

@@ -82,6 +82,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
}
func (pe *PeriodicalExecutor) Wait() {
pe.Flush()
pe.wgBarrier.Guard(func() {
pe.waitGroup.Wait()
})

View File

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

View File

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

View File

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

View File

@@ -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)
}

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/cache"

View File

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

View File

@@ -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...)
}

View File

@@ -42,6 +42,12 @@ type (
red.Cmdable
}
// GeoLocation is used with GeoAdd to add geospatial location.
GeoLocation = red.GeoLocation
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
GeoRadiusQuery = red.GeoRadiusQuery
GeoPos = red.GeoPos
Pipeliner = red.Pipeliner
// Z represents sorted set member.
@@ -173,6 +179,107 @@ func (s *Redis) Expireat(key string, expireTime int64) error {
}, acceptable)
}
func (s *Redis) GeoAdd(key string, geoLocation ...*GeoLocation) (val int64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoAdd(key, geoLocation...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoDist(key string, member1, member2, unit string) (val float64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoDist(key, member1, member2, unit).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoHash(key string, members ...string) (val []string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoHash(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadius(key, longitude, latitude, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadiusByMember(key, member, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoPos(key string, members ...string) (val []*GeoPos, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoPos(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) Get(key string) (val string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
red "github.com/go-redis/redis"
"github.com/stretchr/testify/assert"
)
@@ -816,6 +816,38 @@ func TestRedisBlpopEx(t *testing.T) {
})
}
func TestRedisGeo(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
var geoLocation = []*GeoLocation{{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, {Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}}
v, err := client.GeoAdd("sicily", geoLocation...)
assert.Nil(t, err)
assert.Equal(t, int64(2), v)
v2, err := client.GeoDist("sicily", "Palermo", "Catania", "m")
assert.Nil(t, err)
assert.Equal(t, 166274, int(v2))
// GeoHash not support
v3, err := client.GeoPos("sicily", "Palermo", "Catania")
assert.Nil(t, err)
assert.Equal(t, int64(v3[0].Longitude), int64(13))
assert.Equal(t, int64(v3[0].Latitude), int64(38))
assert.Equal(t, int64(v3[1].Longitude), int64(15))
assert.Equal(t, int64(v3[1].Latitude), int64(37))
v4, err := client.GeoRadius("sicily", 15, 37, &red.GeoRadiusQuery{WithDist: true, Unit: "km", Radius: 200})
assert.Nil(t, err)
assert.Equal(t, int64(v4[0].Dist), int64(190))
assert.Equal(t, int64(v4[1].Dist), int64(56))
var geoLocation2 = []*GeoLocation{{Longitude: 13.583333, Latitude: 37.316667, Name: "Agrigento"}}
v5, err := client.GeoAdd("sicily", geoLocation2...)
assert.Nil(t, err)
assert.Equal(t, int64(1), v5)
v6, err := client.GeoRadiusByMember("sicily", "Agrigento", &red.GeoRadiusQuery{Unit: "km", Radius: 100})
assert.Nil(t, err)
assert.Equal(t, v6[0].Name, "Agrigento")
assert.Equal(t, v6[1].Name, "Palermo")
})
}
func runOnRedis(t *testing.T, fn func(client *Redis)) {
s, err := miniredis.Run()
assert.Nil(t, err)

View File

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

View File

@@ -14,13 +14,14 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"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
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
doc/images/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

BIN
doc/images/benchmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
doc/images/resilience.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -5,7 +5,7 @@ go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.16
github.com/tal-tech/go-zero v1.0.27
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

View File

@@ -36,6 +36,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
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/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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=
@@ -46,6 +47,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -122,9 +124,11 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
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/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
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=
@@ -175,6 +179,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -210,6 +215,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -232,11 +238,14 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.16 h1:oT7sOFftEUdD/XcXF0xEugX9yhnw4DcQkeMNFLi5KO8=
github.com/tal-tech/go-zero v1.0.16/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
github.com/tal-tech/go-zero v1.0.27 h1:QMIbaTxibMc/OsO5RTAuKZ8ndbl2dGN6pITQEtp2x/A=
github.com/tal-tech/go-zero v1.0.27/go.mod h1:JtNXlsh/CgeIHyQnt5C5M2IcSevW7V0NAnqO93TQgm8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
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/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
@@ -382,6 +391,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -393,6 +403,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ var (
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
cacheBookBookPrefix = "cache#Book#book#"
bookPrefix = "cache#Book#book#"
)
type (
@@ -46,9 +46,9 @@ func (m *BookModel) Insert(data Book) (sql.Result, error) {
}
func (m *BookModel) FindOne(book string) (*Book, error) {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, book)
var resp Book
err := m.QueryRow(&resp, bookBookKey, func(conn sqlx.SqlConn, v interface{}) error {
err := m.QueryRow(&resp, bookKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + bookRows + ` from ` + m.table + ` where book = ? limit 1`
return conn.QueryRow(v, query, book)
})
@@ -63,20 +63,19 @@ func (m *BookModel) FindOne(book string) (*Book, error) {
}
func (m *BookModel) Update(data Book) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, data.Book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, data.Book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
return conn.Exec(query, data.Price, data.Book)
}, bookBookKey)
}, bookKey)
return err
}
func (m *BookModel) Delete(book string) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where book = ?`
return conn.Exec(query, book)
}, bookBookKey)
}, bookKey)
return err
}

View File

@@ -5,7 +5,7 @@ go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.16
github.com/tal-tech/go-zero v1.0.27
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

View File

@@ -37,6 +37,7 @@ 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/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=
@@ -48,6 +49,7 @@ github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 h1:9778zj477h/VauD8
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -124,10 +126,12 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
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/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=
@@ -179,6 +183,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -215,6 +220,7 @@ 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/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/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
@@ -238,12 +244,15 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.16 h1:oT7sOFftEUdD/XcXF0xEugX9yhnw4DcQkeMNFLi5KO8=
github.com/tal-tech/go-zero v1.0.16/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
github.com/tal-tech/go-zero v1.0.27 h1:QMIbaTxibMc/OsO5RTAuKZ8ndbl2dGN6pITQEtp2x/A=
github.com/tal-tech/go-zero v1.0.27/go.mod h1:JtNXlsh/CgeIHyQnt5C5M2IcSevW7V0NAnqO93TQgm8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
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/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/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
@@ -390,6 +399,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -401,6 +411,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

13
go.mod
View File

@@ -5,8 +5,8 @@ go 1.13
require (
github.com/ClickHouse/clickhouse-go v1.4.3
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/alicebob/miniredis/v2 v2.14.1
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
@@ -20,12 +20,11 @@ require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/gops v0.3.7
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,12 +39,12 @@ 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
go.uber.org/automaxprocs v1.3.0
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
@@ -59,7 +58,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
)

26
go.sum
View File

@@ -11,10 +11,10 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -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=
@@ -112,8 +114,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -145,8 +145,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
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=
@@ -207,6 +207,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
@@ -248,6 +249,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=
@@ -275,8 +278,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
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=
@@ -417,6 +420,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
@@ -446,6 +450,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=

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,11 +26,11 @@ func ContentSecurityHandler(decrypters map[string]codec.RsaDecrypter, tolerance
case http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut:
header, err := security.ParseContentSecurity(decrypters, r)
if err != nil {
logx.Infof("Signature parse failed, X-Content-Security: %s, error: %s",
logx.Errorf("Signature parse failed, X-Content-Security: %s, error: %s",
r.Header.Get(contentSecurity), err.Error())
executeCallbacks(w, r, next, strict, httpx.CodeSignatureInvalidHeader, callbacks)
} else if code := security.VerifySignature(r, header, tolerance); code != httpx.CodeSignaturePass {
logx.Infof("Signature verification failed, X-Content-Security: %s",
logx.Errorf("Signature verification failed, X-Content-Security: %s",
r.Header.Get(contentSecurity))
executeCallbacks(w, r, next, strict, code, callbacks)
} else if r.ContentLength > 0 && header.Encrypted() {
@@ -54,7 +54,7 @@ func executeCallbacks(w http.ResponseWriter, r *http.Request, next http.Handler,
func handleVerificationFailure(w http.ResponseWriter, r *http.Request, next http.Handler, strict bool, code int) {
if strict {
w.WriteHeader(http.StatusUnauthorized)
w.WriteHeader(http.StatusForbidden)
} else {
next.ServeHTTP(w, r)
}

View File

@@ -113,7 +113,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
timestamp: time.Now().Add(timeDiff).Unix(),
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -122,7 +122,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
timestamp: time.Now().Add(-timeDiff).Unix(),
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -148,7 +148,7 @@ func TestContentSecurityHandler(t *testing.T) {
crypt: true,
timestamp: time.Now().Add(-timeDiff).Unix(),
fingerprint: "badone",
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -157,7 +157,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
missHeader: true,
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodHead,
@@ -171,7 +171,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: false,
signature: "badone",
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
}

View File

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

View File

@@ -3,12 +3,33 @@ package httpx
import (
"encoding/json"
"net/http"
"sync"
"github.com/tal-tech/go-zero/core/logx"
)
var (
errorHandler func(error) (int, interface{})
lock sync.RWMutex
)
func Error(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
lock.RLock()
handler := errorHandler
lock.RUnlock()
if handler == nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
code, body := errorHandler(err)
e, ok := body.(error)
if ok {
http.Error(w, e.Error(), code)
} else {
WriteJson(w, code, body)
}
}
func Ok(w http.ResponseWriter) {
@@ -19,6 +40,12 @@ func OkJson(w http.ResponseWriter, v interface{}) {
WriteJson(w, http.StatusOK, v)
}
func SetErrorHandler(handler func(error) (int, interface{})) {
lock.Lock()
defer lock.Unlock()
errorHandler = handler
}
func WriteJson(w http.ResponseWriter, code int, v interface{}) {
w.Header().Set(ContentType, ApplicationJson)
w.WriteHeader(code)

View File

@@ -19,13 +19,65 @@ func init() {
}
func TestError(t *testing.T) {
const body = "foo"
w := tracedResponseWriter{
headers: make(map[string][]string),
const (
body = "foo"
wrappedBody = `"foo"`
)
tests := []struct {
name string
input string
errorHandler func(error) (int, interface{})
expectBody string
expectCode int
}{
{
name: "default error handler",
input: body,
expectBody: body,
expectCode: http.StatusBadRequest,
},
{
name: "customized error handler return string",
input: body,
errorHandler: func(err error) (int, interface{}) {
return http.StatusForbidden, err.Error()
},
expectBody: wrappedBody,
expectCode: http.StatusForbidden,
},
{
name: "customized error handler return error",
input: body,
errorHandler: func(err error) (int, interface{}) {
return http.StatusForbidden, err
},
expectBody: body,
expectCode: http.StatusForbidden,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
w := tracedResponseWriter{
headers: make(map[string][]string),
}
if test.errorHandler != nil {
lock.RLock()
prev := errorHandler
lock.RUnlock()
SetErrorHandler(test.errorHandler)
defer func() {
lock.Lock()
errorHandler = prev
lock.Unlock()
}()
}
Error(&w, errors.New(test.input))
assert.Equal(t, test.expectCode, w.code)
assert.Equal(t, test.expectBody, strings.TrimSpace(w.builder.String()))
})
}
Error(&w, errors.New(body))
assert.Equal(t, http.StatusBadRequest, w.code)
assert.Equal(t, body, strings.TrimSpace(w.builder.String()))
}
func TestOk(t *testing.T) {

View File

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

View File

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

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -1,15 +1,13 @@
package format
import (
"bufio"
"errors"
"fmt"
"go/format"
"go/scanner"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tal-tech/go-zero/core/errorx"
@@ -18,8 +16,11 @@ import (
"github.com/urfave/cli"
)
var (
reg = regexp.MustCompile("type (?P<name>.*)[\\s]+{")
const (
leftParenthesis = "("
rightParenthesis = ")"
leftBrace = "{"
rightBrace = "}"
)
func GoFormatApi(c *cli.Context) error {
@@ -94,69 +95,32 @@ func ApiFormatByPath(apiFilePath string) error {
}
func apiFormat(data string) (string, error) {
r := reg.ReplaceAllStringFunc(data, func(m string) string {
parts := reg.FindStringSubmatch(m)
if len(parts) < 2 {
return m
}
if !strings.Contains(m, "struct") {
return "type " + parts[1] + " struct {"
}
return m
})
apiStruct, err := parser.ParseApi(r)
_, err := parser.ParseApi(data)
if err != nil {
return "", err
}
info := strings.TrimSpace(apiStruct.Info)
if len(apiStruct.Service) == 0 {
return data, nil
}
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
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
}
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:]))
var builder strings.Builder
s := bufio.NewScanner(strings.NewReader(data))
var tapCount = 0
for s.Scan() {
line := strings.TrimSpace(s.Text())
noCommentLine := util.RemoveComment(line)
if noCommentLine == rightParenthesis || noCommentLine == rightBrace {
tapCount -= 1
}
return "", err
}
var result string
if len(strings.TrimSpace(info)) > 0 {
result += strings.TrimSpace(info) + "\n\n"
}
if len(strings.TrimSpace(apiStruct.Imports)) > 0 {
result += strings.TrimSpace(apiStruct.Imports) + "\n\n"
}
if len(strings.TrimSpace(string(fs))) > 0 {
result += strings.TrimSpace(string(fs)) + "\n\n"
}
if len(strings.TrimSpace(apiStruct.Service)) > 0 {
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
}
return result, nil
}
func countRune(s string, r rune) int {
count := 0
for _, c := range s {
if c == r {
count++
if tapCount < 0 {
line = strings.TrimSuffix(line, rightBrace)
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
tapCount += 1
}
}
util.WriteIndent(&builder, tapCount)
builder.WriteString(line + "\n")
if strings.HasSuffix(noCommentLine, leftParenthesis) || strings.HasSuffix(noCommentLine, leftBrace) {
tapCount += 1
}
}
return count
return strings.TrimSpace(builder.String()), nil
}

View 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)
}

View File

@@ -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,11 +53,10 @@ 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
@@ -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)
}

View File

@@ -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 = `
@@ -21,24 +22,33 @@ info(
version: 1.0
)
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
// TODO: test
// {
type Request struct { // TODO: test
// TOOD
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // }
} // TODO: test
// TODO: test
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)
}
`
@@ -185,6 +195,130 @@ service A-api {
}
`
const apiRouteTest = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + `
}
service A-api {
@handler NormalHandler
get /greet/from/:name(Request) returns (Response)
@handler NoResponseHandler
get /greet/from/:sex(Request)
@handler NoRequestHandler
get /greet/from/request returns (Response)
@handler NoRequestNoResponseHandler
get /greet/from
}
`
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)
}
`
const noStructTagApi = `
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type XXX {}
type (
Response {
Message string ` + "`" + `json:"message"` + "`" + `
}
A {}
B struct {}
)
service A-api {
@handler GreetHandler
get /greet/from/:name(Request) returns (Response)
}
`
const nestTypeApi = `
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
XXX struct {
}
}
service A-api {
@handler GreetHandler
get /greet/from/:name(Request)
}
`
func TestParser(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
@@ -198,13 +332,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)
}
@@ -221,7 +355,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)
@@ -248,10 +382,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)
}
@@ -267,8 +398,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)
}
@@ -333,9 +464,109 @@ func TestApiHasNoRequestBody(t *testing.T) {
validate(t, filename)
}
func TestApiRoutes(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(apiRouteTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func 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 TestNoStructApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(noStructTagApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
spec, err := parser.Parse()
assert.Nil(t, err)
assert.Equal(t, len(spec.Types), 5)
validate(t, filename)
}
func TestNestTypeApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(nestTypeApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
_, err = parser.NewParser(filename)
assert.NotNil(t, err)
}
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 {
@@ -346,6 +577,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 {

View File

@@ -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

View File

@@ -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

View File

@@ -103,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())

View File

@@ -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

View File

@@ -49,8 +49,9 @@ func genMiddleware(dir string, middlewares []string) error {
"name": strings.Title(name),
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -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, "/", "")
}

View File

@@ -90,8 +90,9 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
"middlewareAssignment": middlewareAssignment,
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -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)
}
}
}
}

View File

@@ -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, "//")

View File

@@ -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
}

View File

@@ -3,6 +3,7 @@ package new
import (
"os"
"path/filepath"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
@@ -11,25 +12,25 @@ import (
)
const apiTemplate = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
type Response {
Message string ` + "`" + `json:"message"` + "`" + `
}
service {{.name}}-api {
@handler GreetHandler
get /greet/from/:name(Request) returns (Response);
@handler {{.handler}}Handler
get /from/:name(Request) returns (Response);
}
`
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)
@@ -53,11 +54,12 @@ func NewService(c *cli.Context) error {
defer fp.Close()
t := template.Must(template.New("template").Parse(apiTemplate))
if err := t.Execute(fp, map[string]string{
"name": dirName,
"name": dirName,
"handler": strings.Title(dirName),
}); err != nil {
return err
}
err = gogen.DoGenProject(apiFilePath, abs, true)
err = gogen.DoGenProject(apiFilePath, abs)
return err
}

View File

@@ -0,0 +1,268 @@
package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
const (
tokenInfo = "info"
tokenImport = "import"
tokenType = "type"
tokenService = "service"
tokenServiceAnnotation = "@server"
tokenStruct = "struct"
)
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, _ 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 += newline + 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
line = util.RemoveComment(line)
if len(strings.Fields(line)) != 2 {
return nil, errors.New("import syntax error: " + line)
}
api.Imports += newline + line
return &apiRootState{s.baseState}, nil
}
func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
var braceCount = 0
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
if braceCount == 0 {
line = mayInsertStructKeyword(line)
}
api.Type += newline + newline + line
line = strings.TrimSpace(line)
line = util.RemoveComment(line)
token = ""
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if strings.HasSuffix(line, string(rightBrace)) {
blockCount--
braceCount--
}
if strings.HasSuffix(line, string(rightParenthesis)) {
blockCount--
}
if braceCount >= 2 {
return nil, errors.New("nested type not supported: " + line)
}
if braceCount < 0 {
line = strings.TrimSuffix(line, string(rightBrace))
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
}
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 += newline + line
line = strings.TrimSpace(line)
line = util.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 mayInsertStructKeyword(line string) string {
line = util.RemoveComment(line)
if !strings.HasSuffix(line, leftBrace) && !strings.HasSuffix(line, string(rightBrace)) {
return line
}
fields := strings.Fields(line)
if stringx.Contains(fields, tokenStruct) ||
stringx.Contains(fields, tokenStruct+leftBrace) ||
stringx.Contains(fields, tokenStruct+leftBrace+string(rightBrace)) ||
len(fields) <= 1 {
return line
}
var insertIndex int
if fields[0] == tokenType {
insertIndex = 2
} else {
insertIndex = 1
}
if insertIndex >= len(fields) {
return line
}
var result []string
result = append(result, fields[:insertIndex]...)
result = append(result, tokenStruct)
result = append(result, fields[insertIndex:]...)
return strings.Join(result, " ")
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -3,6 +3,7 @@ package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -14,9 +15,8 @@ import (
)
type Parser struct {
r *bufio.Reader
typeDef string
api *ApiStruct
r *bufio.Reader
api *ApiStruct
}
func NewParser(filename string) (*Parser, error) {
@@ -34,39 +34,57 @@ 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,
api: apiStruct,
r: bufio.NewReader(buffer),
api: apiStruct,
}, nil
}
func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
api = new(spec.ApiSpec)
var sp = StructParser{Src: p.typeDef}
var sp = StructParser{Src: p.api.Type}
types, err := sp.Parse()
if err != nil {
return nil, err
}
api.Types = types
var lineNumber = p.api.serviceBeginLine
st := newRootState(p.r, &lineNumber)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -16,4 +16,5 @@ const (
multilineBeginTag = '>'
multilineEndTag = '<'
semicolon = ';'
newline = "\n"
)

View File

@@ -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

View File

@@ -57,10 +57,8 @@ type (
}
Service struct {
Name string
Annotations []Annotation
Routes []Route
Groups []Group
Name string
Groups []Group
}
Type struct {

View File

@@ -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)

View File

@@ -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)

View File

@@ -26,19 +26,6 @@ func MaybeCreateFile(dir, subdir, file string) (fp *os.File, created bool, err e
return
}
func ClearAndOpenFile(fpath string) (*os.File, error) {
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return nil, err
}
_, err = f.WriteString("")
if err != nil {
return nil, err
}
return f, nil
}
func WrapErr(err error, message string) error {
return errors.New(message + ", " + err.Error())
}
@@ -75,3 +62,17 @@ 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")
}
}
func RemoveComment(line string) string {
var commentIdx = strings.Index(line, "//")
if commentIdx >= 0 {
return strings.TrimSpace(line[:commentIdx])
}
return strings.TrimSpace(line)
}

View File

@@ -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
}

View 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,
})
}

View File

@@ -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(),
})
}

View File

@@ -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
}

View File

@@ -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}}]
`

View File

@@ -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",
@@ -98,10 +98,6 @@ var (
Name: "api",
Usage: "the api file",
},
cli.BoolFlag{
Name: "force",
Usage: "force override the exist files",
},
},
Action: gogen.GoCommand,
},
@@ -188,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,
},
@@ -209,9 +201,13 @@ var (
Name: "new",
Usage: `generate rpc demo service`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
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,
@@ -224,10 +220,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,
},
@@ -239,17 +231,21 @@ 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.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
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,
@@ -278,7 +274,7 @@ var (
},
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|underline,default is lower",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
cli.BoolFlag{
Name: "cache, c",
@@ -313,7 +309,7 @@ var (
},
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|underline,default is lower",
Usage: "the file naming style, lower|camel|snake, default is lower",
},
cli.BoolFlag{
Name: "idea",

View File

@@ -26,7 +26,8 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
* 生成代码示例
``` 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)
}
```
## 用法
@@ -212,16 +218,18 @@ OPTIONS:
```
NAME:
goctl model mysql ddl - generate mysql model from ddl
goctl model mysql ddl - generate mysql model from ddl
USAGE:
goctl model mysql ddl [command options] [arguments...]
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]
OPTIONS:
--src value, -s value the path or path globbing patterns of the ddl
--dir value, -d value the target dir
--cache, -c generate code with cache [optional]
--idea for idea plugin [optional]
```
* datasource
@@ -233,22 +241,26 @@ OPTIONS:
help
```
NAME:
goctl model mysql datasource - generate model from datasource
NAME:
goctl model mysql datasource - generate model from datasource
USAGE:
goctl model mysql datasource [command options] [arguments...]
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]
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
--idea for idea plugin [optional]
```
示例用法请参考[用法](./example/generator.sh)
> NOTE: goctl model mysql ddl/datasource 均新增了一个`--style`参数,用于标记文件命名风格。
目前仅支持redis缓存如果选择带缓存模式即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码目前仅支持单索引字段除全文索引外对于联合索引我们默认认为不需要带缓存且不属于通用型代码因此没有放在代码生成行列如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
* 不带缓存模式

View File

@@ -68,30 +68,3 @@ func FieldNames(in interface{}) []string {
}
return out
}
func FieldNamesAlias(in interface{}, alias string) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
tagName := ""
if tagv := fi.Tag.Get(dbTag); tagv != "" {
tagName = tagv
} else {
tagName = fi.Name
}
if len(alias) > 0 {
tagName = alias + "." + tagName
}
out = append(out, tagName)
}
return out
}

View File

@@ -17,6 +17,8 @@ import (
"github.com/urfave/cli"
)
var errNotMatched = errors.New("sql not matched")
const (
flagSrc = "src"
flagDir = "dir"
@@ -33,6 +35,20 @@ func MysqlDDL(ctx *cli.Context) error {
cache := ctx.Bool(flagCache)
idea := ctx.Bool(flagIdea)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
return fromDDl(src, dir, namingStyle, cache, idea)
}
func MyDataSource(ctx *cli.Context) error {
url := strings.TrimSpace(ctx.String(flagUrl))
dir := strings.TrimSpace(ctx.String(flagDir))
cache := ctx.Bool(flagCache)
idea := ctx.Bool(flagIdea)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
pattern := strings.TrimSpace(ctx.String(flagTable))
return fromDataSource(url, pattern, dir, namingStyle, cache, idea)
}
func fromDDl(src, dir, namingStyle string, cache, idea bool) error {
log := console.NewConsole(idea)
src = strings.TrimSpace(src)
if len(src) == 0 {
@@ -40,7 +56,7 @@ func MysqlDDL(ctx *cli.Context) error {
}
switch namingStyle {
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
case "":
namingStyle = gen.NamingLower
default:
@@ -52,29 +68,29 @@ func MysqlDDL(ctx *cli.Context) error {
return err
}
if len(files) == 0 {
return errNotMatched
}
var source []string
for _, file := range files {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
source = append(source, string(data))
}
generator := gen.NewDefaultGenerator(strings.Join(source, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
err = generator.Start(cache)
generator, err := gen.NewDefaultGenerator(dir, namingStyle, gen.WithConsoleOption(log))
if err != nil {
log.Error("%v", err)
return err
}
return nil
err = generator.StartFromDDL(strings.Join(source, "\n"), cache)
return err
}
func MyDataSource(ctx *cli.Context) error {
url := strings.TrimSpace(ctx.String(flagUrl))
dir := strings.TrimSpace(ctx.String(flagDir))
cache := ctx.Bool(flagCache)
idea := ctx.Bool(flagIdea)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
pattern := strings.TrimSpace(ctx.String(flagTable))
func fromDataSource(url, pattern, dir, namingStyle string, cache, idea bool) error {
log := console.NewConsole(idea)
if len(url) == 0 {
log.Error("%v", "expected data source of mysql, but nothing found")
@@ -87,7 +103,7 @@ func MyDataSource(ctx *cli.Context) error {
}
switch namingStyle {
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
case "":
namingStyle = gen.NamingLower
default:
@@ -100,10 +116,8 @@ func MyDataSource(ctx *cli.Context) error {
}
logx.Disable()
conn := sqlx.NewMysql(url)
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
db := sqlx.NewMysql(databaseSource)
m := model.NewDDLModel(conn)
im := model.NewInformationSchemaModel(db)
tables, err := im.GetAllTables(cfg.DBName)
@@ -111,7 +125,7 @@ func MyDataSource(ctx *cli.Context) error {
return err
}
var matchTables []string
matchTables := make(map[string][]*model.Column)
for _, item := range tables {
match, err := filepath.Match(pattern, item)
if err != nil {
@@ -121,24 +135,22 @@ func MyDataSource(ctx *cli.Context) error {
if !match {
continue
}
matchTables = append(matchTables, item)
columns, err := im.FindByTableName(cfg.DBName, item)
if err != nil {
return err
}
matchTables[item] = columns
}
if len(matchTables) == 0 {
return errors.New("no tables matched")
}
ddl, err := m.ShowDDL(matchTables...)
generator, err := gen.NewDefaultGenerator(dir, namingStyle, gen.WithConsoleOption(log))
if err != nil {
log.Error("%v", err)
return nil
return err
}
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
err = generator.StartFromInformationSchema(cfg.DBName, matchTables, cache)
return err
}

View File

@@ -0,0 +1,75 @@
package command
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
var sql = "-- 用户表 --\nCREATE TABLE `user` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\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 `name_index` (`name`),\n UNIQUE KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;\n\n"
func TestFromDDl(t *testing.T) {
err := fromDDl("./user.sql", t.TempDir(), gen.NamingCamel, true, false)
assert.Equal(t, errNotMatched, err)
// case dir is not exists
unknownDir := filepath.Join(t.TempDir(), "test", "user.sql")
err = fromDDl(unknownDir, t.TempDir(), gen.NamingCamel, true, false)
assert.True(t, func() bool {
switch err.(type) {
case *os.PathError:
return true
default:
return false
}
}())
// case empty src
err = fromDDl("", t.TempDir(), gen.NamingCamel, true, false)
if err != nil {
assert.Equal(t, "expected path or path globbing patterns, but nothing found", err.Error())
}
// case unknown naming style
tmp := filepath.Join(t.TempDir(), "user.sql")
err = fromDDl(tmp, t.TempDir(), "lower1", true, false)
if err != nil {
assert.Equal(t, "unexpected naming style: lower1", err.Error())
}
tempDir := filepath.Join(t.TempDir(), "test")
err = util.MkdirIfNotExist(tempDir)
if err != nil {
return
}
user1Sql := filepath.Join(tempDir, "user1.sql")
user2Sql := filepath.Join(tempDir, "user2.sql")
err = ioutil.WriteFile(user1Sql, []byte(sql), os.ModePerm)
if err != nil {
return
}
err = ioutil.WriteFile(user2Sql, []byte(sql), os.ModePerm)
if err != nil {
return
}
_, err = os.Stat(user1Sql)
assert.Nil(t, err)
_, err = os.Stat(user2Sql)
assert.Nil(t, err)
err = fromDDl(filepath.Join(tempDir, "user*.sql"), tempDir, gen.NamingLower, true, false)
assert.Nil(t, err)
_, err = os.Stat(filepath.Join(tempDir, "usermodel.go"))
assert.Nil(t, err)
}

View File

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

View File

@@ -1,11 +0,0 @@
#!/bin/bash
# generate model with cache from ddl
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
# generate model with cache from data source
#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

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# generate model with cache from ddl
fromDDL:
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
# generate model with cache from data source
user=root
password=password
datasource=127.0.0.1:3306
database=gozero
fromDataSource:
goctl model mysql datasource -url="$(user):$(password)@tcp($(datasource))/$(database)" -table="*" -dir ./model/cache -c -style camel

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