mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-06-14 01:41:57 +08:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c560e8ba | ||
|
|
bed494d904 | ||
|
|
2dfecda465 | ||
|
|
3ebb1e0221 | ||
|
|
348184904c | ||
|
|
7a27fa50a1 | ||
|
|
8d4951c990 | ||
|
|
6e57f6c527 | ||
|
|
b9ac51b6c3 | ||
|
|
702e8d79ce | ||
|
|
95a9dabf8b | ||
|
|
bae66c49c2 | ||
|
|
e0afe0b4bb | ||
|
|
24fb29a356 | ||
|
|
71083b5e64 | ||
|
|
1174f17bd9 | ||
|
|
d6d8fc21d8 | ||
|
|
9592639cb4 | ||
|
|
abcb28e506 | ||
|
|
a92f65580c | ||
|
|
3819f67cf4 | ||
|
|
295c8d2934 | ||
|
|
88da8685dd | ||
|
|
c7831ac96d | ||
|
|
e898761762 | ||
|
|
13d1c5cd00 | ||
|
|
16bfb1b7be | ||
|
|
ef4d4968d6 | ||
|
|
7b4a5e3ec6 | ||
|
|
e6df21e0d2 | ||
|
|
0a2c2d1eca | ||
|
|
a5fb29a6f0 | ||
|
|
f8da301e57 | ||
|
|
cb9075b737 | ||
|
|
3f389a55c2 | ||
|
|
afbd565d87 | ||
|
|
d629acc2b7 | ||
|
|
f32c6a9b28 | ||
|
|
95aa65efb9 | ||
|
|
3806e66cf1 | ||
|
|
bd430baf52 | ||
|
|
48f4154ea8 | ||
|
|
2599e0d28d | ||
|
|
12327fa07d | ||
|
|
57079bf4a4 | ||
|
|
7f6eceb5a3 | ||
|
|
7d7cb836af | ||
|
|
f87d9d1dda | ||
|
|
856b5aadb1 | ||
|
|
f7d778e0ed | ||
|
|
88333ee77f | ||
|
|
e76f44a35b | ||
|
|
c9ec22d5f4 | ||
|
|
afffc1048b | ||
|
|
d0b76b1d9a | ||
|
|
b004b070d7 | ||
|
|
677d581bd1 | ||
|
|
b776468e69 | ||
|
|
4c9315e984 | ||
|
|
668a7011c4 | ||
|
|
cc07a1d69b | ||
|
|
7f99a3baa8 | ||
|
|
9504418462 | ||
|
|
b144a2335c | ||
|
|
7b9ed7a313 | ||
|
|
3d2e9fcb84 | ||
|
|
2b993424c1 | ||
|
|
5e87b33b23 | ||
|
|
9b7cc43dcb | ||
|
|
000b28cf84 | ||
|
|
9fd16cd278 | ||
|
|
b71429e16b | ||
|
|
a13b48c33e | ||
|
|
033525fea8 | ||
|
|
607fc3297a | ||
|
|
4287877b74 | ||
|
|
2b7545ce11 | ||
|
|
60925c1164 | ||
|
|
1c9e81aa28 | ||
|
|
db7dcaa120 | ||
|
|
099d44054d | ||
|
|
f5f873c6bd | ||
|
|
6dbd3eada9 | ||
|
|
cf2d20a211 | ||
|
|
91bfc093f4 | ||
|
|
cf33aae91d | ||
|
|
c9494c8bc7 | ||
|
|
1fd2ef9347 | ||
|
|
efffb40fa3 | ||
|
|
9c8f31cf83 | ||
|
|
96cb7af728 | ||
|
|
41964f9d52 | ||
|
|
fe0d0687f5 | ||
|
|
1c1e4bca86 | ||
|
|
1abe21aa2a | ||
|
|
cee170f3e9 | ||
|
|
907efd92c9 | ||
|
|
737cd4751a | ||
|
|
dfe6e88529 | ||
|
|
85a815bea0 | ||
|
|
aa3c391919 | ||
|
|
c9b0ac1ee4 | ||
|
|
33faab61a3 |
@@ -3,19 +3,15 @@ package bloom
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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 TestRedisBitSet_New_Set_Test(t *testing.T) {
|
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error("Miniredis could not start")
|
defer clean()
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||||
isSetBefore, err := bitSet.check([]uint{0})
|
isSetBefore, err := bitSet.check([]uint{0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,13 +42,10 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedisBitSet_Add(t *testing.T) {
|
func TestRedisBitSet_Add(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error("Miniredis could not start")
|
defer clean()
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
filter := New(store, "test_key", 64)
|
filter := New(store, "test_key", 64)
|
||||||
assert.Nil(t, filter.Add([]byte("hello")))
|
assert.Nil(t, filter.Add([]byte("hello")))
|
||||||
assert.Nil(t, filter.Add([]byte("world")))
|
assert.Nil(t, filter.Add([]byte("world")))
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ func (rw *RollingWindow) updateOffset() {
|
|||||||
span := rw.span()
|
span := rw.span()
|
||||||
if span > 0 {
|
if span > 0 {
|
||||||
offset := rw.offset
|
offset := rw.offset
|
||||||
// reset expired buckets
|
|
||||||
start := offset + 1
|
start := offset + 1
|
||||||
steps := start + span
|
steps := start + span
|
||||||
var remainder int
|
var remainder int
|
||||||
@@ -84,15 +83,16 @@ func (rw *RollingWindow) updateOffset() {
|
|||||||
remainder = steps - rw.size
|
remainder = steps - rw.size
|
||||||
steps = rw.size
|
steps = rw.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset expired buckets
|
||||||
for i := start; i < steps; i++ {
|
for i := start; i < steps; i++ {
|
||||||
rw.win.resetBucket(i)
|
rw.win.resetBucket(i)
|
||||||
offset = i
|
|
||||||
}
|
}
|
||||||
for i := 0; i < remainder; i++ {
|
for i := 0; i < remainder; i++ {
|
||||||
rw.win.resetBucket(i)
|
rw.win.resetBucket(i)
|
||||||
offset = i
|
|
||||||
}
|
}
|
||||||
rw.offset = offset
|
|
||||||
|
rw.offset = (offset + span) % rw.size
|
||||||
rw.lastTime = timex.Now()
|
rw.lastTime = timex.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ func (tw *TimingWheel) removeTask(key interface{}) {
|
|||||||
|
|
||||||
timer := val.(*positionEntry)
|
timer := val.(*positionEntry)
|
||||||
timer.item.removed = true
|
timer.item.removed = true
|
||||||
|
tw.timers.Del(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *TimingWheel) run() {
|
func (tw *TimingWheel) run() {
|
||||||
@@ -248,7 +249,6 @@ func (tw *TimingWheel) scanAndRunTasks(l *list.List) {
|
|||||||
if task.removed {
|
if task.removed {
|
||||||
next := e.Next()
|
next := e.Next()
|
||||||
l.Remove(e)
|
l.Remove(e)
|
||||||
tw.timers.Del(task.key)
|
|
||||||
e = next
|
e = next
|
||||||
continue
|
continue
|
||||||
} else if task.circle > 0 {
|
} else if task.circle > 0 {
|
||||||
@@ -301,6 +301,7 @@ func (tw *TimingWheel) setTask(task *timingEntry) {
|
|||||||
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
|
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
|
||||||
if val, ok := tw.timers.Get(task.key); ok {
|
if val, ok := tw.timers.Get(task.key); ok {
|
||||||
timer := val.(*positionEntry)
|
timer := val.(*positionEntry)
|
||||||
|
timer.item = task
|
||||||
timer.pos = pos
|
timer.pos = pos
|
||||||
} else {
|
} else {
|
||||||
tw.timers.Set(task.key, &positionEntry{
|
tw.timers.Set(task.key, &positionEntry{
|
||||||
|
|||||||
@@ -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) {
|
func BenchmarkTimingWheel(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd0:2379
|
- http://etcd0.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -107,7 +107,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd1:2379
|
- http://etcd1.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -179,7 +179,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd2:2379
|
- http://etcd2.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -251,7 +251,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd3:2379
|
- http://etcd3.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -323,7 +323,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd4:2379
|
- http://etcd4.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
|
|||||||
11
core/errorx/callchain.go
Normal file
11
core/errorx/callchain.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package errorx
|
||||||
|
|
||||||
|
func Chain(fns ...func() error) error {
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := fn(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
27
core/errorx/callchain_test.go
Normal file
27
core/errorx/callchain_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package errorx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChain(t *testing.T) {
|
||||||
|
var errDummy = errors.New("dummy")
|
||||||
|
assert.Nil(t, Chain(func() error {
|
||||||
|
return nil
|
||||||
|
}, func() error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
assert.Equal(t, errDummy, Chain(func() error {
|
||||||
|
return errDummy
|
||||||
|
}, func() error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
assert.Equal(t, errDummy, Chain(func() error {
|
||||||
|
return nil
|
||||||
|
}, func() error {
|
||||||
|
return errDummy
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) Wait() {
|
func (pe *PeriodicalExecutor) Wait() {
|
||||||
|
pe.Flush()
|
||||||
pe.wgBarrier.Guard(func() {
|
pe.wgBarrier.Guard(func() {
|
||||||
pe.waitGroup.Wait()
|
pe.waitGroup.Wait()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package limit
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"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) {
|
func TestPeriodLimit_Take(t *testing.T) {
|
||||||
@@ -33,16 +34,16 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPeriodLimit(t *testing.T, opts ...LimitOption) {
|
func testPeriodLimit(t *testing.T, opts ...LimitOption) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
seconds = 1
|
seconds = 1
|
||||||
total = 100
|
total = 100
|
||||||
quota = 5
|
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
|
var allowed, hitQuota, overQuota int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
val, err := l.Take("first")
|
val, err := l.Take("first")
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"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"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,16 +45,16 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_Take(t *testing.T) {
|
func TestTokenLimit_Take(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
rate = 5
|
rate = 5
|
||||||
burst = 10
|
burst = 10
|
||||||
)
|
)
|
||||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
|
||||||
var allowed int
|
var allowed int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
time.Sleep(time.Second / time.Duration(total))
|
time.Sleep(time.Second / time.Duration(total))
|
||||||
@@ -66,16 +67,16 @@ func TestTokenLimit_Take(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_TakeBurst(t *testing.T) {
|
func TestTokenLimit_TakeBurst(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
rate = 5
|
rate = 5
|
||||||
burst = 10
|
burst = 10
|
||||||
)
|
)
|
||||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
|
||||||
var allowed int
|
var allowed int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
if l.Allow() {
|
if l.Allow() {
|
||||||
|
|||||||
13
core/stores/cache/cache_test.go
vendored
13
core/stores/cache/cache_test.go
vendored
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/errorx"
|
"github.com/tal-tech/go-zero/core/errorx"
|
||||||
"github.com/tal-tech/go-zero/core/hash"
|
"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"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
"github.com/tal-tech/go-zero/core/syncx"
|
"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) {
|
func TestCache_SetDel(t *testing.T) {
|
||||||
const total = 1000
|
const total = 1000
|
||||||
r1, clean1, err := createMiniRedis()
|
r1, clean1, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean1()
|
defer clean1()
|
||||||
r2, clean2, err := createMiniRedis()
|
r2, clean2, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean2()
|
defer clean2()
|
||||||
conf := ClusterConf{
|
conf := ClusterConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: r1.Addr(),
|
Host: r1.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: r2.Addr(),
|
Host: r2.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
@@ -123,13 +124,13 @@ func TestCache_SetDel(t *testing.T) {
|
|||||||
|
|
||||||
func TestCache_OneNode(t *testing.T) {
|
func TestCache_OneNode(t *testing.T) {
|
||||||
const total = 1000
|
const total = 1000
|
||||||
r, clean, err := createMiniRedis()
|
r, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
conf := ClusterConf{
|
conf := ClusterConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: r.Addr(),
|
Host: r.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
|
|||||||
8
core/stores/cache/cachenode.go
vendored
8
core/stores/cache/cachenode.go
vendored
@@ -175,12 +175,12 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
|
|||||||
}
|
}
|
||||||
if fresh {
|
if fresh {
|
||||||
return nil
|
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)
|
return jsonx.Unmarshal(val.([]byte), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
core/stores/cache/cachenode_test.go
vendored
43
core/stores/cache/cachenode_test.go
vendored
@@ -9,12 +9,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/mathx"
|
"github.com/tal-tech/go-zero/core/mathx"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"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"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
"github.com/tal-tech/go-zero/core/syncx"
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,12 +27,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_DelCache(t *testing.T) {
|
func TestCacheNode_DelCache(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
@@ -49,9 +50,9 @@ func TestCacheNode_DelCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_InvalidCache(t *testing.T) {
|
func TestCacheNode_InvalidCache(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
s, err := miniredis.Run()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer s.Close()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
||||||
@@ -70,12 +71,12 @@ func TestCacheNode_InvalidCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_Take(t *testing.T) {
|
func TestCacheNode_Take(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSharedCalls(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
@@ -91,18 +92,18 @@ func TestCacheNode_Take(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value", str)
|
assert.Equal(t, "value", str)
|
||||||
assert.Nil(t, cn.GetCache("any", &str))
|
assert.Nil(t, cn.GetCache("any", &str))
|
||||||
val, err := s.Get("any")
|
val, err := store.Get("any")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, `"value"`, val)
|
assert.Equal(t, `"value"`, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_TakeNotFound(t *testing.T) {
|
func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSharedCalls(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
@@ -116,18 +117,18 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Equal(t, errTestNotFound, err)
|
assert.Equal(t, errTestNotFound, err)
|
||||||
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
||||||
val, err := s.Get("any")
|
val, err := store.Get("any")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, `*`, val)
|
assert.Equal(t, `*`, val)
|
||||||
|
|
||||||
s.Set("any", "*")
|
store.Set("any", "*")
|
||||||
err = cn.Take(&str, "any", func(v interface{}) error {
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.Equal(t, errTestNotFound, err)
|
assert.Equal(t, errTestNotFound, err)
|
||||||
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
||||||
|
|
||||||
s.Del("any")
|
store.Del("any")
|
||||||
var errDummy = errors.New("dummy")
|
var errDummy = errors.New("dummy")
|
||||||
err = cn.Take(&str, "any", func(v interface{}) error {
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
return errDummy
|
return errDummy
|
||||||
@@ -136,12 +137,12 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_TakeWithExpire(t *testing.T) {
|
func TestCacheNode_TakeWithExpire(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSharedCalls(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
@@ -157,18 +158,18 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value", str)
|
assert.Equal(t, "value", str)
|
||||||
assert.Nil(t, cn.GetCache("any", &str))
|
assert.Nil(t, cn.GetCache("any", &str))
|
||||||
val, err := s.Get("any")
|
val, err := store.Get("any")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, `"value"`, val)
|
assert.Equal(t, `"value"`, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_String(t *testing.T) {
|
func TestCacheNode_String(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSharedCalls(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
@@ -176,16 +177,16 @@ func TestCacheNode_String(t *testing.T) {
|
|||||||
stat: NewCacheStat("any"),
|
stat: NewCacheStat("any"),
|
||||||
errNotFound: errors.New("any"),
|
errNotFound: errors.New("any"),
|
||||||
}
|
}
|
||||||
assert.Equal(t, s.Addr(), cn.String())
|
assert.Equal(t, store.Addr, cn.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheValueWithBigInt(t *testing.T) {
|
func TestCacheValueWithBigInt(t *testing.T) {
|
||||||
s, clean, err := createMiniRedis()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
barrier: syncx.NewSharedCalls(),
|
barrier: syncx.NewSharedCalls(),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
|
|||||||
22
core/stores/cache/util_test.go
vendored
22
core/stores/cache/util_test.go
vendored
@@ -2,11 +2,8 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/lang"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFormatKeys(t *testing.T) {
|
func TestFormatKeys(t *testing.T) {
|
||||||
@@ -27,22 +24,3 @@ func TestTotalWeights(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Equal(t, 1, val)
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ type (
|
|||||||
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
||||||
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
||||||
Zscore(key string, value string) (int64, error)
|
Zscore(key string, value string) (int64, error)
|
||||||
|
Zrevrank(key, field string) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterStore struct {
|
clusterStore struct {
|
||||||
@@ -635,6 +636,15 @@ func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, sto
|
|||||||
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
|
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
|
||||||
|
node, err := cs.getRedis(key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.Zrevrank(key, field)
|
||||||
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/hash"
|
"github.com/tal-tech/go-zero/core/hash"
|
||||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||||
@@ -516,6 +516,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, err = store.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
_, err = store.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrevrank("key", "value")
|
||||||
|
assert.NotNil(t, err)
|
||||||
_, err = store.Zadds("key", redis.Pair{
|
_, err = store.Zadds("key", redis.Pair{
|
||||||
Key: "value2",
|
Key: "value2",
|
||||||
Score: 6,
|
Score: 6,
|
||||||
@@ -640,6 +642,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 5,
|
Score: 5,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
rank, err = client.Zrevrank("key", "value1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(1), rank)
|
||||||
val, err = client.Zadds("key", redis.Pair{
|
val, err = client.Zadds("key", redis.Pair{
|
||||||
Key: "value2",
|
Key: "value2",
|
||||||
Score: 6,
|
Score: 6,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
"github.com/globalsign/mgo/bson"
|
"github.com/globalsign/mgo/bson"
|
||||||
"github.com/stretchr/testify/assert"
|
"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/cache"
|
||||||
"github.com/tal-tech/go-zero/core/stores/mongo"
|
"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"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -27,12 +27,10 @@ func init() {
|
|||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
@@ -73,12 +71,10 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatDbFails(t *testing.T) {
|
func TestStatDbFails(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
@@ -97,12 +93,10 @@ func TestStatDbFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatFromMemory(t *testing.T) {
|
func TestStatFromMemory(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const postgreDriverName = "postgres"
|
const postgresDriverName = "postgres"
|
||||||
|
|
||||||
func NewPostgre(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
func NewPostgres(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
||||||
return sqlx.NewSqlConn(postgreDriverName, datasource, opts...)
|
return sqlx.NewSqlConn(postgresDriverName, datasource, opts...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ type (
|
|||||||
red.Cmdable
|
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
|
Pipeliner = red.Pipeliner
|
||||||
|
|
||||||
// Z represents sorted set member.
|
// Z represents sorted set member.
|
||||||
@@ -173,6 +179,107 @@ func (s *Redis) Expireat(key string, expireTime int64) error {
|
|||||||
}, acceptable)
|
}, 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) {
|
func (s *Redis) Get(key string) (val string, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
@@ -1273,6 +1380,20 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Redis) Zrevrank(key string, field string) (val int64, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.ZRevRank(key, field).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Redis) String() string {
|
func (s *Redis) String() string {
|
||||||
return s.Addr
|
return s.Addr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
red "github.com/go-redis/redis"
|
red "github.com/go-redis/redis"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -584,6 +584,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
rank, err := client.Zrank("key", "value2")
|
rank, err := client.Zrank("key", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(1), rank)
|
assert.Equal(t, int64(1), rank)
|
||||||
|
rank, err = client.Zrevrank("key", "value1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(2), rank)
|
||||||
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
|
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
_, err = client.Zrank("key", "value4")
|
_, err = client.Zrank("key", "value4")
|
||||||
@@ -710,6 +713,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, len(pairs))
|
assert.Equal(t, 0, len(pairs))
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrevrank("key", "value")
|
||||||
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,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)) {
|
func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
||||||
s, err := miniredis.Run()
|
s, err := miniredis.Run()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|||||||
28
core/stores/redis/redistest/redistest.go
Normal file
28
core/stores/redis/redistest/redistest.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package redistest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/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
|
||||||
|
}
|
||||||
@@ -14,12 +14,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/fx"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"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/cache"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"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"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,17 +32,15 @@ func init() {
|
|||||||
|
|
||||||
func TestCachedConn_GetCache(t *testing.T) {
|
func TestCachedConn_GetCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
var value string
|
var value string
|
||||||
err = c.GetCache("any", &value)
|
err = c.GetCache("any", &value)
|
||||||
assert.Equal(t, ErrNotFound, err)
|
assert.Equal(t, ErrNotFound, err)
|
||||||
s.Set("any", `"value"`)
|
r.Set("any", `"value"`)
|
||||||
err = c.GetCache("any", &value)
|
err = c.GetCache("any", &value)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value", value)
|
assert.Equal(t, "value", value)
|
||||||
@@ -48,12 +48,10 @@ func TestCachedConn_GetCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
@@ -73,16 +71,14 @@ func TestStat(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: s.Addr(),
|
Host: r.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
@@ -124,12 +120,10 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
cache.WithNotFoundExpiry(time.Second))
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
@@ -213,18 +207,13 @@ func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := miniredis.Run()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
resetStats()
|
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),
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
cache.WithNotFoundExpiry(time.Second))
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
@@ -261,14 +250,10 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
|||||||
for k, v := range caches {
|
for k, v := range caches {
|
||||||
t.Run(k+"/"+v, func(t *testing.T) {
|
t.Run(k+"/"+v, func(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
s.FlushAll()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
cache.WithNotFoundExpiry(time.Second))
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
@@ -320,12 +305,10 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatDbFails(t *testing.T) {
|
func TestStatDbFails(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
@@ -343,12 +326,10 @@ func TestStatDbFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatFromMemory(t *testing.T) {
|
func TestStatFromMemory(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
var all sync.WaitGroup
|
var all sync.WaitGroup
|
||||||
@@ -403,10 +384,9 @@ func TestStatFromMemory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRow(t *testing.T) {
|
func TestCachedConnQueryRow(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -415,7 +395,6 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
ran = true
|
ran = true
|
||||||
@@ -423,7 +402,7 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
actualValue, err := s.Get(key)
|
actualValue, err := r.Get(key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
var actual string
|
var actual string
|
||||||
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
||||||
@@ -433,10 +412,9 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -445,7 +423,6 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
@@ -454,7 +431,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
actualValue, err := s.Get(key)
|
actualValue, err := r.Get(key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
var actual string
|
var actual string
|
||||||
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
||||||
@@ -464,16 +441,14 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRowNotFound(t *testing.T) {
|
func TestQueryRowNotFound(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const key = "user"
|
const key = "user"
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran int
|
var ran int
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
@@ -486,13 +461,11 @@ func TestQueryRowNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExec(t *testing.T) {
|
func TestCachedConnExec(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
|
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -500,25 +473,26 @@ func TestCachedConnExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExecDropCache(t *testing.T) {
|
func TestCachedConnExecDropCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, err := miniredis.Run()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer fx.DoWithTimeout(func() error {
|
||||||
}
|
r.Close()
|
||||||
|
return nil
|
||||||
|
}, time.Second)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
value = "any"
|
value = "any"
|
||||||
)
|
)
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
c := NewNodeConn(&conn, redis.NewRedis(r.Addr(), redis.NodeType), cache.WithExpiry(time.Second*30))
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return conn.Exec("delete from user_table where id='kevin'")
|
return conn.Exec("delete from user_table where id='kevin'")
|
||||||
}, key)
|
}, key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, conn.execValue)
|
assert.True(t, conn.execValue)
|
||||||
_, err = s.Get(key)
|
_, err = r.Get(key)
|
||||||
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return nil, errors.New("foo")
|
return nil, errors.New("foo")
|
||||||
@@ -539,13 +513,11 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRows(t *testing.T) {
|
func TestCachedConnQueryRows(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
var users []string
|
var users []string
|
||||||
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
|
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
|
||||||
@@ -554,13 +526,11 @@ func TestCachedConnQueryRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnTransact(t *testing.T) {
|
func TestCachedConnTransact(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
err = c.Transact(func(session sqlx.Session) error {
|
err = c.Transact(func(session sqlx.Session) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -570,10 +540,9 @@ func TestCachedConnTransact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRowNoCache(t *testing.T) {
|
func TestQueryRowNoCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -581,7 +550,6 @@ func TestQueryRowNoCache(t *testing.T) {
|
|||||||
)
|
)
|
||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
|
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
|
||||||
user = value
|
user = value
|
||||||
ran = true
|
ran = true
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func NewSharedCalls() SharedCalls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
|
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 {
|
if done {
|
||||||
return c.val, c.err
|
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) {
|
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 {
|
if done {
|
||||||
return c.val, false, c.err
|
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
|
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()
|
g.lock.Lock()
|
||||||
if c, ok := g.calls[key]; ok {
|
if c, ok := g.calls[key]; ok {
|
||||||
g.lock.Unlock()
|
g.lock.Unlock()
|
||||||
|
|||||||
BIN
doc/images/architecture-en.png
Normal file
BIN
doc/images/architecture-en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
BIN
doc/images/architecture.png
Normal file
BIN
doc/images/architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 333 KiB |
BIN
doc/images/benchmark.png
Normal file
BIN
doc/images/benchmark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
doc/images/go-zero.png
Normal file
BIN
doc/images/go-zero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
doc/images/resilience-en.png
Normal file
BIN
doc/images/resilience-en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
BIN
doc/images/resilience.jpg
Normal file
BIN
doc/images/resilience.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
@@ -5,7 +5,7 @@ go 1.15
|
|||||||
require (
|
require (
|
||||||
github.com/golang/mock v1.4.3
|
github.com/golang/mock v1.4.3
|
||||||
github.com/golang/protobuf v1.4.2
|
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
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||||
google.golang.org/grpc v1.29.1
|
google.golang.org/grpc v1.29.1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
|
||||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
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-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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/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 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
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.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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
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.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 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
|
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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
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.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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
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=
|
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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
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=
|
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/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/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.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/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/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=
|
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/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 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.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-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 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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.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.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 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
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=
|
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/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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
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/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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
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.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-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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"bookstore/rpc/add/internal/config"
|
"bookstore/rpc/add/internal/config"
|
||||||
|
add "bookstore/rpc/add/internal/pb"
|
||||||
"bookstore/rpc/add/internal/server"
|
"bookstore/rpc/add/internal/server"
|
||||||
"bookstore/rpc/add/internal/svc"
|
"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/conf"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ package adder
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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"
|
"github.com/tal-tech/go-zero/zrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
AddReq = add.AddReq
|
||||||
|
AddResp = add.AddResp
|
||||||
|
|
||||||
Adder interface {
|
Adder interface {
|
||||||
Add(ctx context.Context, in *AddReq) (*AddResp, error)
|
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) {
|
func (m *defaultAdder) Add(ctx context.Context, in *AddReq) (*AddResp, error) {
|
||||||
var request add.AddReq
|
adder := add.NewAdderClient(m.cli.Conn())
|
||||||
bts, err := jsonx.Marshal(in)
|
return adder.Add(ctx, 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Code generated by goctl. DO NOT EDIT!
|
|
||||||
// Source: add.proto
|
|
||||||
|
|
||||||
package adder
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var errJsonConvert = errors.New("json convert error")
|
|
||||||
|
|
||||||
type (
|
|
||||||
AddReq struct {
|
|
||||||
Book string `json:"book,omitempty"`
|
|
||||||
Price int64 `json:"price,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
AddResp struct {
|
|
||||||
Ok bool `json:"ok,omitempty"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -3,8 +3,8 @@ package logic
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
add "bookstore/rpc/add/internal/pb"
|
||||||
"bookstore/rpc/add/internal/svc"
|
"bookstore/rpc/add/internal/svc"
|
||||||
add "bookstore/rpc/add/pb"
|
|
||||||
"bookstore/rpc/model"
|
"bookstore/rpc/model"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ It has these top-level messages:
|
|||||||
*/
|
*/
|
||||||
package add
|
package add
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
|
||||||
import fmt "fmt"
|
|
||||||
import math "math"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "golang.org/x/net/context"
|
"fmt"
|
||||||
grpc "google.golang.org/grpc"
|
"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.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"bookstore/rpc/add/internal/logic"
|
"bookstore/rpc/add/internal/logic"
|
||||||
|
add "bookstore/rpc/add/internal/pb"
|
||||||
"bookstore/rpc/add/internal/svc"
|
"bookstore/rpc/add/internal/svc"
|
||||||
add "bookstore/rpc/add/pb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdderServer struct {
|
type AdderServer struct {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"bookstore/rpc/check/internal/config"
|
"bookstore/rpc/check/internal/config"
|
||||||
|
check "bookstore/rpc/check/internal/pb"
|
||||||
"bookstore/rpc/check/internal/server"
|
"bookstore/rpc/check/internal/server"
|
||||||
"bookstore/rpc/check/internal/svc"
|
"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/conf"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ package checker
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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"
|
"github.com/tal-tech/go-zero/zrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
CheckReq = check.CheckReq
|
||||||
|
CheckResp = check.CheckResp
|
||||||
|
|
||||||
Checker interface {
|
Checker interface {
|
||||||
Check(ctx context.Context, in *CheckReq) (*CheckResp, error)
|
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) {
|
func (m *defaultChecker) Check(ctx context.Context, in *CheckReq) (*CheckResp, error) {
|
||||||
var request check.CheckReq
|
checker := check.NewCheckerClient(m.cli.Conn())
|
||||||
bts, err := jsonx.Marshal(in)
|
return checker.Check(ctx, 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Code generated by goctl. DO NOT EDIT!
|
|
||||||
// Source: check.proto
|
|
||||||
|
|
||||||
package checker
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var errJsonConvert = errors.New("json convert error")
|
|
||||||
|
|
||||||
type (
|
|
||||||
CheckReq struct {
|
|
||||||
Book string `json:"book,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckResp struct {
|
|
||||||
Found bool `json:"found,omitempty"`
|
|
||||||
Price int64 `json:"price,omitempty"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -3,8 +3,8 @@ package logic
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
check "bookstore/rpc/check/internal/pb"
|
||||||
"bookstore/rpc/check/internal/svc"
|
"bookstore/rpc/check/internal/svc"
|
||||||
check "bookstore/rpc/check/pb"
|
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ It has these top-level messages:
|
|||||||
*/
|
*/
|
||||||
package check
|
package check
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
|
||||||
import fmt "fmt"
|
|
||||||
import math "math"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "golang.org/x/net/context"
|
"fmt"
|
||||||
grpc "google.golang.org/grpc"
|
"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.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"bookstore/rpc/check/internal/logic"
|
"bookstore/rpc/check/internal/logic"
|
||||||
|
check "bookstore/rpc/check/internal/pb"
|
||||||
"bookstore/rpc/check/internal/svc"
|
"bookstore/rpc/check/internal/svc"
|
||||||
check "bookstore/rpc/check/pb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckerServer struct {
|
type CheckerServer struct {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
|
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
|
||||||
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
|
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
|
||||||
|
|
||||||
cacheBookBookPrefix = "cache#Book#book#"
|
bookPrefix = "cache#Book#book#"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -46,9 +46,9 @@ func (m *BookModel) Insert(data Book) (sql.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *BookModel) FindOne(book string) (*Book, 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
|
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`
|
query := `select ` + bookRows + ` from ` + m.table + ` where book = ? limit 1`
|
||||||
return conn.QueryRow(v, query, book)
|
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 {
|
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) {
|
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
|
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
|
||||||
return conn.Exec(query, data.Price, data.Book)
|
return conn.Exec(query, data.Price, data.Book)
|
||||||
}, bookBookKey)
|
}, bookKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BookModel) Delete(book string) error {
|
func (m *BookModel) Delete(book string) error {
|
||||||
|
bookKey := fmt.Sprintf("%s%v", bookPrefix, book)
|
||||||
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
|
|
||||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := `delete from ` + m.table + ` where book = ?`
|
query := `delete from ` + m.table + ` where book = ?`
|
||||||
return conn.Exec(query, book)
|
return conn.Exec(query, book)
|
||||||
}, bookBookKey)
|
}, bookKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func main() {
|
|||||||
Port: *port,
|
Port: *port,
|
||||||
Timeout: *timeout,
|
Timeout: *timeout,
|
||||||
MaxConns: 500,
|
MaxConns: 500,
|
||||||
})
|
}, rest.WithNotAllowedHandler(rest.CorsHandler()))
|
||||||
defer engine.Stop()
|
defer engine.Stop()
|
||||||
|
|
||||||
engine.Use(first)
|
engine.Use(first)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ func main() {
|
|||||||
var allowed, denied int32
|
var allowed, denied int32
|
||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
for i := 0; i < *threads; i++ {
|
for i := 0; i < *threads; i++ {
|
||||||
|
i := i
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ go 1.15
|
|||||||
require (
|
require (
|
||||||
github.com/golang/mock v1.4.3
|
github.com/golang/mock v1.4.3
|
||||||
github.com/golang/protobuf v1.4.2
|
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
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||||
google.golang.org/grpc v1.29.1
|
google.golang.org/grpc v1.29.1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/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 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-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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/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 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
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.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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
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.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 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
|
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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
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 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
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=
|
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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
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=
|
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/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 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.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/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/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=
|
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/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 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.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-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 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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.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 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
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 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
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=
|
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/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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
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/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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
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.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-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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
|||||||
13
go.mod
13
go.mod
@@ -5,8 +5,8 @@ go 1.13
|
|||||||
require (
|
require (
|
||||||
github.com/ClickHouse/clickhouse-go v1.4.3
|
github.com/ClickHouse/clickhouse-go v1.4.3
|
||||||
github.com/DATA-DOG/go-sqlmock v1.4.1
|
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 v2.14.1
|
||||||
github.com/alicebob/miniredis v2.5.0+incompatible
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/dchest/siphash v1.2.1
|
github.com/dchest/siphash v1.2.1
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/emicklei/proto v1.9.0
|
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/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||||
github.com/golang/mock v1.4.3
|
github.com/golang/mock v1.4.3
|
||||||
github.com/golang/protobuf v1.4.2
|
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/gops v0.3.7
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3 // 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/justinas/alice v1.2.0
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // 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/pierrec/lz4 v2.5.1+incompatible // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.5.1
|
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/spaolacci/murmur3 v1.1.0
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
|
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/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.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698
|
||||||
go.uber.org/automaxprocs v1.3.0
|
go.uber.org/automaxprocs v1.3.0
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||||
@@ -59,7 +58,7 @@ require (
|
|||||||
google.golang.org/protobuf v1.25.0
|
google.golang.org/protobuf v1.25.0
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||||
gopkg.in/h2non/gock.v1 v1.0.15
|
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
|
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
|
||||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -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/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-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/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-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
|
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
|
||||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
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/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 v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
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/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 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-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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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/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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
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.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
|
||||||
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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
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=
|
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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/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/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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
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/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 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.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/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 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
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 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
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.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
||||||
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 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
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=
|
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.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.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.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.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 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
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.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-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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||||
|
|||||||
18
readme-en.md
18
readme-en.md
@@ -1,3 +1,5 @@
|
|||||||
|
<img align="right" width="150px" src="doc/images/go-zero.png">
|
||||||
|
|
||||||
# go-zero
|
# go-zero
|
||||||
|
|
||||||
English | [简体中文](readme.md)
|
English | [简体中文](readme.md)
|
||||||
@@ -23,7 +25,7 @@ Advantages of go-zero:
|
|||||||
* auto validate the request parameters from clients
|
* auto validate the request parameters from clients
|
||||||
* plenty of builtin microservice management and concurrent toolkits
|
* plenty of builtin microservice management and concurrent toolkits
|
||||||
|
|
||||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
<img src="doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||||
|
|
||||||
## 1. Backgrounds of go-zero
|
## 1. Backgrounds of go-zero
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ go-zero is a web and rpc framework that integrates lots of engineering practices
|
|||||||
|
|
||||||
As below, go-zero protects the system with couple layers and mechanisms:
|
As below, go-zero protects the system with couple layers and mechanisms:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 4. Future development plans of go-zero
|
## 4. Future development plans of go-zero
|
||||||
|
|
||||||
@@ -119,9 +121,7 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
}
|
}
|
||||||
|
|
||||||
service greet-api {
|
service greet-api {
|
||||||
@server(
|
@handler GreetHandler
|
||||||
handler: GreetHandler
|
|
||||||
)
|
|
||||||
get /greet/from/:name(Request) returns (Response);
|
get /greet/from/:name(Request) returns (Response);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -129,7 +129,7 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
the .api files also can be generate by goctl, like below:
|
the .api files also can be generate by goctl, like below:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
goctl api -o greet.api
|
goctl api -o greet.api
|
||||||
```
|
```
|
||||||
|
|
||||||
3. generate the go server side code
|
3. generate the go server side code
|
||||||
@@ -164,6 +164,8 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
cd greet
|
cd greet
|
||||||
|
go mod init
|
||||||
|
go mod tidy
|
||||||
go run greet.go -f etc/greet-api.yaml
|
go run greet.go -f etc/greet-api.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,7 +174,7 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
you can check it by curl:
|
you can check it by curl:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -i http://localhost:8888/greet/from/you
|
curl -i http://localhost:8888/from/you
|
||||||
```
|
```
|
||||||
|
|
||||||
the response looks like:
|
the response looks like:
|
||||||
@@ -198,7 +200,7 @@ go get -u github.com/tal-tech/go-zero
|
|||||||
|
|
||||||
## 7. Benchmark
|
## 7. Benchmark
|
||||||
|
|
||||||

|

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

|

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

|

|
||||||
|
|
||||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||||
|
|
||||||
## 7. 文档
|
## 7. 文档
|
||||||
|
|
||||||
* API文档 (逐步完善中)
|
* API 文档 (逐步完善中)
|
||||||
|
|
||||||
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
||||||
|
|
||||||
* awesome系列
|
* awesome 系列
|
||||||
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
* [快速构建高并发微服务](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)
|
* [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)
|
* [通过 MapReduce 降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
|
||||||
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
|
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
|
||||||
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
||||||
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
||||||
* [基于prometheus的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
* [基于 prometheus 的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||||
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
|
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
|
||||||
* [快速构建jwt鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
* [快速构建 jwt 鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
||||||
|
|
||||||
## 9. 微信交流群
|
## 8. 微信交流群
|
||||||
|
|
||||||
加群之前有劳给一个star,一个小小的star是作者们回答海量问题的动力。
|
|
||||||
|
|
||||||
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
||||||
|
|
||||||
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
|
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
|
||||||
|
|
||||||
如果您发现bug请及时提issue,我们会尽快确认并修改。
|
如果您发现 ***bug*** 请及时提 ***issue***,我们会尽快确认并修改。
|
||||||
|
|
||||||
扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢谢!
|
为了防止广告用户、识别技术同行,请 ***star*** 后加我时注明 **github** 当前 ***star*** 数,我再拉进 **go-zero** 群,感谢!
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
加我之前有劳点一下 ***star***,一个小小的 ***star*** 是作者们回答海量问题的动力🤝
|
||||||
|
|
||||||
|
<img src="https://gitee.com/kevwan/static/raw/master/images/wechat.jpg" alt="wechat" width="300" />
|
||||||
|
|
||||||
|
项目地址:[https://github.com/tal-tech/go-zero](https://github.com/tal-tech/go-zero)
|
||||||
|
|
||||||
|
码云地址:[https://gitee.com/kevwan/go-zero](https://gitee.com/kevwan/go-zero) (国内用户可访问gitee,每日自动从github同步代码)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type (
|
|||||||
PrivateKeys []PrivateKeyConf
|
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 {
|
// type Config struct {
|
||||||
// zrpc.RpcConf
|
// zrpc.RpcConf
|
||||||
// rest.RestConf
|
// rest.RestConf
|
||||||
@@ -28,9 +28,11 @@ type (
|
|||||||
service.ServiceConf
|
service.ServiceConf
|
||||||
Host string `json:",default=0.0.0.0"`
|
Host string `json:",default=0.0.0.0"`
|
||||||
Port int
|
Port int
|
||||||
Verbose bool `json:",optional"`
|
CertFile string `json:",optional"`
|
||||||
MaxConns int `json:",default=10000"`
|
KeyFile string `json:",optional"`
|
||||||
MaxBytes int64 `json:",default=1048576,range=[0:8388608]"`
|
Verbose bool `json:",optional"`
|
||||||
|
MaxConns int `json:",default=10000"`
|
||||||
|
MaxBytes int64 `json:",default=1048576,range=[0:8388608]"`
|
||||||
// milliseconds
|
// milliseconds
|
||||||
Timeout int64 `json:",default=3000"`
|
Timeout int64 `json:",default=3000"`
|
||||||
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *engine) Start() error {
|
func (s *engine) Start() error {
|
||||||
return s.StartWithRouter(router.NewPatRouter())
|
return s.StartWithRouter(router.NewRouter())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *engine) StartWithRouter(router httpx.Router) error {
|
func (s *engine) StartWithRouter(router httpx.Router) error {
|
||||||
@@ -65,7 +65,11 @@ func (s *engine) StartWithRouter(router httpx.Router) error {
|
|||||||
return err
|
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,
|
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ func ContentSecurityHandler(decrypters map[string]codec.RsaDecrypter, tolerance
|
|||||||
case http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut:
|
case http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut:
|
||||||
header, err := security.ParseContentSecurity(decrypters, r)
|
header, err := security.ParseContentSecurity(decrypters, r)
|
||||||
if err != nil {
|
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())
|
r.Header.Get(contentSecurity), err.Error())
|
||||||
executeCallbacks(w, r, next, strict, httpx.CodeSignatureInvalidHeader, callbacks)
|
executeCallbacks(w, r, next, strict, httpx.CodeSignatureInvalidHeader, callbacks)
|
||||||
} else if code := security.VerifySignature(r, header, tolerance); code != httpx.CodeSignaturePass {
|
} 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))
|
r.Header.Get(contentSecurity))
|
||||||
executeCallbacks(w, r, next, strict, code, callbacks)
|
executeCallbacks(w, r, next, strict, code, callbacks)
|
||||||
} else if r.ContentLength > 0 && header.Encrypted() {
|
} 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) {
|
func handleVerificationFailure(w http.ResponseWriter, r *http.Request, next http.Handler, strict bool, code int) {
|
||||||
if strict {
|
if strict {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
} else {
|
} else {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func TestContentSecurityHandler(t *testing.T) {
|
|||||||
strict: true,
|
strict: true,
|
||||||
crypt: true,
|
crypt: true,
|
||||||
timestamp: time.Now().Add(timeDiff).Unix(),
|
timestamp: time.Now().Add(timeDiff).Unix(),
|
||||||
statusCode: http.StatusUnauthorized,
|
statusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
@@ -122,7 +122,7 @@ func TestContentSecurityHandler(t *testing.T) {
|
|||||||
strict: true,
|
strict: true,
|
||||||
crypt: true,
|
crypt: true,
|
||||||
timestamp: time.Now().Add(-timeDiff).Unix(),
|
timestamp: time.Now().Add(-timeDiff).Unix(),
|
||||||
statusCode: http.StatusUnauthorized,
|
statusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
@@ -148,7 +148,7 @@ func TestContentSecurityHandler(t *testing.T) {
|
|||||||
crypt: true,
|
crypt: true,
|
||||||
timestamp: time.Now().Add(-timeDiff).Unix(),
|
timestamp: time.Now().Add(-timeDiff).Unix(),
|
||||||
fingerprint: "badone",
|
fingerprint: "badone",
|
||||||
statusCode: http.StatusUnauthorized,
|
statusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
@@ -157,7 +157,7 @@ func TestContentSecurityHandler(t *testing.T) {
|
|||||||
strict: true,
|
strict: true,
|
||||||
crypt: true,
|
crypt: true,
|
||||||
missHeader: true,
|
missHeader: true,
|
||||||
statusCode: http.StatusUnauthorized,
|
statusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: http.MethodHead,
|
method: http.MethodHead,
|
||||||
@@ -171,7 +171,7 @@ func TestContentSecurityHandler(t *testing.T) {
|
|||||||
strict: true,
|
strict: true,
|
||||||
crypt: false,
|
crypt: false,
|
||||||
signature: "badone",
|
signature: "badone",
|
||||||
statusCode: http.StatusUnauthorized,
|
statusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
rest/handlers.go
Normal file
27
rest/handlers.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
const (
|
||||||
|
allowOrigin = "Access-Control-Allow-Origin"
|
||||||
|
allOrigins = "*"
|
||||||
|
allowMethods = "Access-Control-Allow-Methods"
|
||||||
|
allowHeaders = "Access-Control-Allow-Headers"
|
||||||
|
headers = "Content-Type, Content-Length, Origin"
|
||||||
|
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CorsHandler handles cross domain OPTIONS requests.
|
||||||
|
// At most one origin can be specified, other origins are ignored if given.
|
||||||
|
func CorsHandler(origins ...string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if len(origins) > 0 {
|
||||||
|
w.Header().Set(allowOrigin, origins[0])
|
||||||
|
} else {
|
||||||
|
w.Header().Set(allowOrigin, allOrigins)
|
||||||
|
}
|
||||||
|
w.Header().Set(allowMethods, methods)
|
||||||
|
w.Header().Set(allowHeaders, headers)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
}
|
||||||
42
rest/handlers_test.go
Normal file
42
rest/handlers_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCorsHandlerWithOrigins(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
origins []string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "allow all origins",
|
||||||
|
expect: allOrigins,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow one origin",
|
||||||
|
origins: []string{"local"},
|
||||||
|
expect: "local",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow many origins",
|
||||||
|
origins: []string{"local", "remote"},
|
||||||
|
expect: "local",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler := CorsHandler(test.origins...)
|
||||||
|
handler.ServeHTTP(w, nil)
|
||||||
|
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||||
|
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, Parse(r, &v))
|
||||||
err = Parse(r, &v)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "hello", v.Name)
|
assert.Equal(t, "hello", v.Name)
|
||||||
assert.Equal(t, 18, v.Age)
|
assert.Equal(t, 18, v.Age)
|
||||||
assert.Equal(t, 3.4, v.Percent)
|
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 := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
|
||||||
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
|
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
|
||||||
|
|
||||||
err := Parse(r, &v)
|
assert.Nil(t, Parse(r, &v))
|
||||||
assert.Nil(t, err)
|
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, "kevin", v.Name)
|
||||||
assert.Equal(t, 18, v.Age)
|
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)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, Parse(r, &v))
|
||||||
err = Parse(r, &v)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseOptions(t *testing.T) {
|
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)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?pos=4", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, Parse(r, &v))
|
||||||
err = Parse(r, &v)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkParseRaw(b *testing.B) {
|
func BenchmarkParseRaw(b *testing.B) {
|
||||||
|
|||||||
@@ -3,12 +3,33 @@ package httpx
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorHandler func(error) (int, interface{})
|
||||||
|
lock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
func Error(w http.ResponseWriter, err error) {
|
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) {
|
func Ok(w http.ResponseWriter) {
|
||||||
@@ -19,6 +40,12 @@ func OkJson(w http.ResponseWriter, v interface{}) {
|
|||||||
WriteJson(w, http.StatusOK, v)
|
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{}) {
|
func WriteJson(w http.ResponseWriter, code int, v interface{}) {
|
||||||
w.Header().Set(ContentType, ApplicationJson)
|
w.Header().Set(ContentType, ApplicationJson)
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
|
|||||||
@@ -19,13 +19,65 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
const body = "foo"
|
const (
|
||||||
w := tracedResponseWriter{
|
body = "foo"
|
||||||
headers: make(map[string][]string),
|
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) {
|
func TestOk(t *testing.T) {
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ type Router interface {
|
|||||||
http.Handler
|
http.Handler
|
||||||
Handle(method string, path string, handler http.Handler) error
|
Handle(method string, path string, handler http.Handler) error
|
||||||
SetNotFoundHandler(handler http.Handler)
|
SetNotFoundHandler(handler http.Handler)
|
||||||
|
SetNotAllowedHandler(handler http.Handler)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ func WithPathVars(r *http.Request, params map[string]string) *http.Request {
|
|||||||
type contextKey string
|
type contextKey string
|
||||||
|
|
||||||
func (c contextKey) String() string {
|
func (c contextKey) String() string {
|
||||||
return "rest/internal/context context key" + string(c)
|
return "rest/internal/context key: " + string(c)
|
||||||
}
|
}
|
||||||
|
|||||||
32
rest/internal/context/params_test.go
Normal file
32
rest/internal/context/params_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVars(t *testing.T) {
|
||||||
|
expect := map[string]string{
|
||||||
|
"a": "1",
|
||||||
|
"b": "2",
|
||||||
|
}
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), pathVars, expect))
|
||||||
|
assert.EqualValues(t, expect, Vars(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVarsNil(t *testing.T) {
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, Vars(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextKey(t *testing.T) {
|
||||||
|
ck := contextKey("hello")
|
||||||
|
assert.True(t, strings.Contains(ck.String(), "hello"))
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -10,42 +9,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartHttp(host string, port int, handler http.Handler) error {
|
func StartHttp(host string, port int, handler http.Handler) error {
|
||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
return start(host, port, handler, func(srv *http.Server) error {
|
||||||
server := buildHttpServer(addr, handler)
|
return srv.ListenAndServe()
|
||||||
return StartServer(server)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
|
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
|
||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
return start(host, port, handler, func(srv *http.Server) error {
|
||||||
if server, err := buildHttpsServer(addr, handler, certFile, keyFile); err != nil {
|
// certFile and keyFile are set in buildHttpsServer
|
||||||
return err
|
return srv.ListenAndServeTLS(certFile, keyFile)
|
||||||
} else {
|
|
||||||
return StartServer(server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartServer(srv *http.Server) error {
|
|
||||||
proc.AddWrapUpListener(func() {
|
|
||||||
srv.Shutdown(context.Background())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return srv.ListenAndServe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHttpServer(addr string, handler http.Handler) *http.Server {
|
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) error {
|
||||||
return &http.Server{Addr: addr, Handler: handler}
|
server := &http.Server{
|
||||||
}
|
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
waitForCalled := proc.AddWrapUpListener(func() {
|
||||||
|
server.Shutdown(context.Background())
|
||||||
|
})
|
||||||
|
defer waitForCalled()
|
||||||
|
|
||||||
config := tls.Config{Certificates: []tls.Certificate{cert}}
|
return run(server)
|
||||||
return &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: handler,
|
|
||||||
TLSConfig: &config,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,19 @@ var (
|
|||||||
ErrInvalidPath = errors.New("path must begin with '/'")
|
ErrInvalidPath = errors.New("path must begin with '/'")
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatRouter struct {
|
type patRouter struct {
|
||||||
trees map[string]*search.Tree
|
trees map[string]*search.Tree
|
||||||
notFound http.Handler
|
notFound http.Handler
|
||||||
|
notAllowed http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPatRouter() httpx.Router {
|
func NewRouter() httpx.Router {
|
||||||
return &PatRouter{
|
return &patRouter{
|
||||||
trees: make(map[string]*search.Tree),
|
trees: make(map[string]*search.Tree),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PatRouter) Handle(method, reqPath string, handler http.Handler) error {
|
func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {
|
||||||
if !validMethod(method) {
|
if !validMethod(method) {
|
||||||
return ErrInvalidMethod
|
return ErrInvalidMethod
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func (pr *PatRouter) Handle(method, reqPath string, handler http.Handler) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PatRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
reqPath := path.Clean(r.URL.Path)
|
reqPath := path.Clean(r.URL.Path)
|
||||||
if tree, ok := pr.trees[r.Method]; ok {
|
if tree, ok := pr.trees[r.Method]; ok {
|
||||||
if result, ok := tree.Search(reqPath); ok {
|
if result, ok := tree.Search(reqPath); ok {
|
||||||
@@ -63,19 +64,29 @@ func (pr *PatRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if allow, ok := pr.methodNotAllowed(r.Method, reqPath); ok {
|
allow, ok := pr.methodNotAllowed(r.Method, reqPath)
|
||||||
|
if !ok {
|
||||||
|
pr.handleNotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.notAllowed != nil {
|
||||||
|
pr.notAllowed.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
w.Header().Set(allowHeader, allow)
|
w.Header().Set(allowHeader, allow)
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
} else {
|
|
||||||
pr.handleNotFound(w, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PatRouter) SetNotFoundHandler(handler http.Handler) {
|
func (pr *patRouter) SetNotFoundHandler(handler http.Handler) {
|
||||||
pr.notFound = handler
|
pr.notFound = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PatRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
func (pr *patRouter) SetNotAllowedHandler(handler http.Handler) {
|
||||||
|
pr.notAllowed = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr *patRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||||
if pr.notFound != nil {
|
if pr.notFound != nil {
|
||||||
pr.notFound.ServeHTTP(w, r)
|
pr.notFound.ServeHTTP(w, r)
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +94,7 @@ func (pr *PatRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *PatRouter) methodNotAllowed(method, path string) (string, bool) {
|
func (pr *patRouter) methodNotAllowed(method, path string) (string, bool) {
|
||||||
var allows []string
|
var allows []string
|
||||||
|
|
||||||
for treeMethod, tree := range pr.trees {
|
for treeMethod, tree := range pr.trees {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func TestPatRouterHandleErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.method, func(t *testing.T) {
|
t.Run(test.method, func(t *testing.T) {
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err := router.Handle(test.method, test.path, nil)
|
err := router.Handle(test.method, test.path, nil)
|
||||||
assert.Error(t, ErrInvalidMethod, err)
|
assert.Error(t, ErrInvalidMethod, err)
|
||||||
})
|
})
|
||||||
@@ -56,17 +56,34 @@ func TestPatRouterHandleErrors(t *testing.T) {
|
|||||||
|
|
||||||
func TestPatRouterNotFound(t *testing.T) {
|
func TestPatRouterNotFound(t *testing.T) {
|
||||||
var notFound bool
|
var notFound bool
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
router.SetNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
router.SetNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
notFound = true
|
notFound = true
|
||||||
}))
|
}))
|
||||||
router.Handle(http.MethodGet, "/a/b", nil)
|
err := router.Handle(http.MethodGet, "/a/b",
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
assert.Nil(t, err)
|
||||||
r, _ := http.NewRequest(http.MethodGet, "/b/c", nil)
|
r, _ := http.NewRequest(http.MethodGet, "/b/c", nil)
|
||||||
w := new(mockedResponseWriter)
|
w := new(mockedResponseWriter)
|
||||||
router.ServeHTTP(w, r)
|
router.ServeHTTP(w, r)
|
||||||
assert.True(t, notFound)
|
assert.True(t, notFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPatRouterNotAllowed(t *testing.T) {
|
||||||
|
var notAllowed bool
|
||||||
|
router := NewRouter()
|
||||||
|
router.SetNotAllowedHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
notAllowed = true
|
||||||
|
}))
|
||||||
|
err := router.Handle(http.MethodGet, "/a/b",
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
r, _ := http.NewRequest(http.MethodPost, "/a/b", nil)
|
||||||
|
w := new(mockedResponseWriter)
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
assert.True(t, notAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPatRouter(t *testing.T) {
|
func TestPatRouter(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
method string
|
method string
|
||||||
@@ -87,7 +104,7 @@ func TestPatRouter(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.method+":"+test.path, func(t *testing.T) {
|
t.Run(test.method+":"+test.path, func(t *testing.T) {
|
||||||
routed := false
|
routed := false
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
routed = true
|
routed = true
|
||||||
assert.Equal(t, 1, len(context.Vars(r)))
|
assert.Equal(t, 1, len(context.Vars(r)))
|
||||||
@@ -125,7 +142,7 @@ func TestParseSlice(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
rt := NewPatRouter()
|
rt := NewRouter()
|
||||||
err = rt.Handle(http.MethodPost, "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
err = rt.Handle(http.MethodPost, "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
Names []string `form:"names"`
|
Names []string `form:"names"`
|
||||||
@@ -149,7 +166,7 @@ func TestParseJsonPost(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
||||||
w http.ResponseWriter, r *http.Request) {
|
w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -181,7 +198,7 @@ func TestParseJsonPostWithIntSlice(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
||||||
w http.ResponseWriter, r *http.Request) {
|
w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -209,7 +226,7 @@ func TestParseJsonPostError(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -237,7 +254,7 @@ func TestParseJsonPostInvalidRequest(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -259,7 +276,7 @@ func TestParseJsonPostRequired(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -282,7 +299,7 @@ func TestParsePath(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -307,7 +324,7 @@ func TestParsePathRequired(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -328,7 +345,7 @@ func TestParseQuery(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -353,7 +370,7 @@ func TestParseQueryRequired(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", nil)
|
r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
Nickname string `form:"nickname"`
|
Nickname string `form:"nickname"`
|
||||||
@@ -373,7 +390,7 @@ func TestParseOptional(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -414,7 +431,7 @@ func TestParseNestedInRequestEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -453,7 +470,7 @@ func TestParsePtrInRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -484,7 +501,7 @@ func TestParsePtrInRequestEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/kevin", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/kevin", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -501,7 +518,7 @@ func TestParseQueryOptional(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -526,7 +543,7 @@ func TestParse(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -564,7 +581,7 @@ func TestParseWrappedRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -596,7 +613,7 @@ func TestParseWrappedGetRequestWithJsonHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -629,7 +646,7 @@ func TestParseWrappedHeadRequestWithJsonHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodHead, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodHead, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -661,7 +678,7 @@ func TestParseWrappedRequestPtr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v WrappedRequest
|
var v WrappedRequest
|
||||||
@@ -684,7 +701,7 @@ func TestParseWithAll(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
Name string `path:"name"`
|
Name string `path:"name"`
|
||||||
@@ -715,7 +732,7 @@ func TestParseWithAllUtf8(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -746,7 +763,7 @@ func TestParseWithMissingForm(t *testing.T) {
|
|||||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -773,7 +790,7 @@ func TestParseWithMissingAllForms(t *testing.T) {
|
|||||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -799,7 +816,7 @@ func TestParseWithMissingJson(t *testing.T) {
|
|||||||
bytes.NewBufferString(`{"location": "shanghai"}`))
|
bytes.NewBufferString(`{"location": "shanghai"}`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -825,7 +842,7 @@ func TestParseWithMissingAllJsons(t *testing.T) {
|
|||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -852,7 +869,7 @@ func TestParseWithMissingPath(t *testing.T) {
|
|||||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -879,7 +896,7 @@ func TestParseWithMissingAllPaths(t *testing.T) {
|
|||||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -906,7 +923,7 @@ func TestParseGetWithContentLengthHeader(t *testing.T) {
|
|||||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||||
r.Header.Set(contentLength, "1024")
|
r.Header.Set(contentLength, "1024")
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -933,7 +950,7 @@ func TestParseJsonPostWithTypeMismatch(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -959,7 +976,7 @@ func TestParseJsonPostWithInt2String(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
v := struct {
|
v := struct {
|
||||||
@@ -980,7 +997,7 @@ func TestParseJsonPostWithInt2String(t *testing.T) {
|
|||||||
func BenchmarkPatRouter(b *testing.B) {
|
func BenchmarkPatRouter(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
router := NewPatRouter()
|
router := NewRouter()
|
||||||
router.Handle(http.MethodGet, "/api/:user/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
router.Handle(http.MethodGet, "/api/:user/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
}))
|
}))
|
||||||
w := &mockedResponseWriter{}
|
w := &mockedResponseWriter{}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/rest/handler"
|
"github.com/tal-tech/go-zero/rest/handler"
|
||||||
"github.com/tal-tech/go-zero/rest/httpx"
|
"github.com/tal-tech/go-zero/rest/httpx"
|
||||||
|
"github.com/tal-tech/go-zero/rest/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -32,6 +34,10 @@ func MustNewServer(c RestConf, opts ...RunOption) *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
|
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
|
||||||
|
if len(opts) > 1 {
|
||||||
|
return nil, errors.New("only one RunOption is allowed")
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.SetUp(); err != nil {
|
if err := c.SetUp(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -103,6 +109,13 @@ func WithJwtTransition(secret, prevSecret string) RouteOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithMiddlewares(ms []Middleware, rs ...Route) []Route {
|
||||||
|
for i := len(ms) - 1; i >= 0; i-- {
|
||||||
|
rs = WithMiddleware(ms[i], rs...)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
||||||
routes := make([]Route, len(rs))
|
routes := make([]Route, len(rs))
|
||||||
|
|
||||||
@@ -118,6 +131,18 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithNotFoundHandler(handler http.Handler) RunOption {
|
||||||
|
rt := router.NewRouter()
|
||||||
|
rt.SetNotFoundHandler(handler)
|
||||||
|
return WithRouter(rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNotAllowedHandler(handler http.Handler) RunOption {
|
||||||
|
rt := router.NewRouter()
|
||||||
|
rt.SetNotAllowedHandler(handler)
|
||||||
|
return WithRouter(rt)
|
||||||
|
}
|
||||||
|
|
||||||
func WithPriority() RouteOption {
|
func WithPriority() RouteOption {
|
||||||
return func(r *featuredRoutes) {
|
return func(r *featuredRoutes) {
|
||||||
r.priority = true
|
r.priority = true
|
||||||
|
|||||||
@@ -12,9 +12,14 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/rest/router"
|
"github.com/tal-tech/go-zero/rest/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNewServer(t *testing.T) {
|
||||||
|
_, err := NewServer(RestConf{}, WithNotFoundHandler(nil), WithNotAllowedHandler(nil))
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithMiddleware(t *testing.T) {
|
func TestWithMiddleware(t *testing.T) {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
router := router.NewPatRouter()
|
router := router.NewRouter()
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
var v struct {
|
var v struct {
|
||||||
Nickname string `form:"nickname"`
|
Nickname string `form:"nickname"`
|
||||||
@@ -68,3 +73,81 @@ func TestWithMiddleware(t *testing.T) {
|
|||||||
"wan": "2020",
|
"wan": "2020",
|
||||||
}, m)
|
}, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultiMiddlewares(t *testing.T) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
router := router.NewRouter()
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var v struct {
|
||||||
|
Nickname string `form:"nickname"`
|
||||||
|
Zipcode int64 `form:"zipcode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := httpx.Parse(r, &v)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = io.WriteString(w, fmt.Sprintf("%s:%s", v.Nickname, m[v.Nickname]))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
rs := WithMiddlewares([]Middleware{
|
||||||
|
func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var v struct {
|
||||||
|
Name string `path:"name"`
|
||||||
|
Year string `path:"year"`
|
||||||
|
}
|
||||||
|
assert.Nil(t, httpx.ParsePath(r, &v))
|
||||||
|
m[v.Name] = v.Year
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var v struct {
|
||||||
|
Name string `form:"nickname"`
|
||||||
|
Zipcode string `form:"zipcode"`
|
||||||
|
}
|
||||||
|
assert.Nil(t, httpx.ParseForm(r, &v))
|
||||||
|
assert.NotEmpty(t, m)
|
||||||
|
m[v.Name] = v.Zipcode + v.Zipcode
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, Route{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/first/:name/:year",
|
||||||
|
Handler: handler,
|
||||||
|
}, Route{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/second/:name/:year",
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
|
||||||
|
urls := []string{
|
||||||
|
"http://hello.com/first/kevin/2017?nickname=whatever&zipcode=200000",
|
||||||
|
"http://hello.com/second/wan/2020?nickname=whatever&zipcode=200000",
|
||||||
|
}
|
||||||
|
for _, route := range rs {
|
||||||
|
assert.Nil(t, router.Handle(route.Method, route.Path, route.Handler))
|
||||||
|
}
|
||||||
|
for _, url := range urls {
|
||||||
|
r, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rr, r)
|
||||||
|
|
||||||
|
assert.Equal(t, "whatever:200000200000", rr.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, map[string]string{
|
||||||
|
"kevin": "2017",
|
||||||
|
"wan": "2020",
|
||||||
|
"whatever": "200000200000",
|
||||||
|
}, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithPriority(t *testing.T) {
|
||||||
|
var fr featuredRoutes
|
||||||
|
WithPriority()(&fr)
|
||||||
|
assert.True(t, fr.priority)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ version := $(shell /bin/date "+%Y-%m-%d %H:%M")
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" goctl.go
|
go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" goctl.go
|
||||||
command -v upx &> /dev/null && upx goctl
|
$(if $(shell command -v upx), upx goctl)
|
||||||
mac:
|
mac:
|
||||||
GOOS=darwin go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-darwin goctl.go
|
GOOS=darwin go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-darwin goctl.go
|
||||||
command -v upx &> /dev/null && upx goctl-darwin
|
$(if $(shell command -v upx), upx goctl-darwin)
|
||||||
win:
|
win:
|
||||||
GOOS=windows go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl.exe goctl.go
|
GOOS=windows go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl.exe goctl.go
|
||||||
command -v upx &> /dev/null && upx goctl.exe
|
$(if $(shell command -v upx), upx goctl.exe)
|
||||||
linux:
|
linux:
|
||||||
GOOS=linux go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-linux goctl.go
|
GOOS=linux go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-linux goctl.go
|
||||||
command -v upx &> /dev/null && upx goctl-linux
|
$(if $(shell command -v upx), upx goctl-linux)
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
const apiTemplate = `info(
|
const apiTemplate = `info(
|
||||||
title: // TODO: add title
|
title: // TODO: add title
|
||||||
desc: // TODO: add description
|
desc: // TODO: add description
|
||||||
author: {{.gitUser}}
|
author: "{{.gitUser}}"
|
||||||
email: {{.gitEmail}}
|
email: "{{.gitEmail}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
@@ -28,14 +28,10 @@ type response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service {{.serviceName}} {
|
service {{.serviceName}} {
|
||||||
@server(
|
@handler GetUser // TODO: set handler name and delete this comment
|
||||||
handler: // TODO: set handler name and delete this comment
|
|
||||||
)
|
|
||||||
get /users/id/:userId(request) returns(response)
|
get /users/id/:userId(request) returns(response)
|
||||||
|
|
||||||
@server(
|
@handler CreateUser // TODO: set handler name and delete this comment
|
||||||
handler: // TODO: set handler name and delete this comment
|
|
||||||
)
|
|
||||||
post /users/create(request)
|
post /users/create(request)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
|
|||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
var builder strings.Builder
|
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")
|
routeComment, _ := util.GetAnnotationValue(route.Annotations, "doc", "summary")
|
||||||
if len(routeComment) == 0 {
|
if len(routeComment) == 0 {
|
||||||
routeComment = "N/A"
|
routeComment = "N/A"
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package format
|
package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/scanner"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/errorx"
|
"github.com/tal-tech/go-zero/core/errorx"
|
||||||
@@ -17,109 +16,111 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
reg = regexp.MustCompile("type (?P<name>.*)[\\s]+{")
|
leftParenthesis = "("
|
||||||
|
rightParenthesis = ")"
|
||||||
|
leftBrace = "{"
|
||||||
|
rightBrace = "}"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GoFormatApi(c *cli.Context) error {
|
func GoFormatApi(c *cli.Context) error {
|
||||||
dir := c.String("dir")
|
useStdin := c.Bool("stdin")
|
||||||
if len(dir) == 0 {
|
|
||||||
return errors.New("missing -dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
printToConsole := c.Bool("p")
|
|
||||||
|
|
||||||
var be errorx.BatchError
|
var be errorx.BatchError
|
||||||
err := filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
if useStdin {
|
||||||
if strings.HasSuffix(path, ".api") {
|
if err := ApiFormatByStdin(); err != nil {
|
||||||
err := ApiFormat(path, printToConsole)
|
be.Add(err)
|
||||||
if err != nil {
|
|
||||||
be.Add(util.WrapErr(err, fi.Name()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
} else {
|
||||||
})
|
dir := c.String("dir")
|
||||||
be.Add(err)
|
if len(dir) == 0 {
|
||||||
|
return errors.New("missing -dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Lstat(dir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(dir + ": No such file or directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||||
|
if strings.HasSuffix(path, ".api") {
|
||||||
|
if err := ApiFormatByPath(path); err != nil {
|
||||||
|
be.Add(util.WrapErr(err, fi.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
be.Add(err)
|
||||||
|
}
|
||||||
if be.NotNil() {
|
if be.NotNil() {
|
||||||
errs := be.Err().Error()
|
scanner.PrintError(os.Stderr, be.Err())
|
||||||
fmt.Println(errs)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return be.Err()
|
return be.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiFormat(path string, printToConsole bool) error {
|
func ApiFormatByStdin() error {
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := reg.ReplaceAllStringFunc(string(data), func(m string) string {
|
result, err := apiFormat(string(data))
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
info := strings.TrimSpace(apiStruct.Info)
|
|
||||||
if len(apiStruct.Service) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
|
_, err = fmt.Print(result)
|
||||||
if err != nil {
|
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:]))
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
if printToConsole {
|
|
||||||
_, err := fmt.Print(result)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(path, []byte(result), os.ModePerm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func countRune(s string, r rune) int {
|
func ApiFormatByPath(apiFilePath string) error {
|
||||||
count := 0
|
data, err := ioutil.ReadFile(apiFilePath)
|
||||||
for _, c := range s {
|
if err != nil {
|
||||||
if c == r {
|
return err
|
||||||
count++
|
}
|
||||||
|
|
||||||
|
result, err := apiFormat(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiFormat(data string) (string, error) {
|
||||||
|
_, err := parser.ParseApi(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
47
tools/goctl/api/format/format_test.go
Normal file
47
tools/goctl/api/format/format_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
notFormattedStr = `
|
||||||
|
type Request struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
formattedStr = `type Request struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInlineTypeNotExist(t *testing.T) {
|
||||||
|
r, err := apiFormat(notFormattedStr)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, r, formattedStr)
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
package gogen
|
package gogen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -14,13 +12,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora"
|
"github.com/logrusorgru/aurora"
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format"
|
apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
|
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
|
||||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpFile = "%s-%d"
|
const tmpFile = "%s-%d"
|
||||||
@@ -30,7 +28,8 @@ var tmpDir = path.Join(os.TempDir(), "goctl")
|
|||||||
func GoCommand(c *cli.Context) error {
|
func GoCommand(c *cli.Context) error {
|
||||||
apiFile := c.String("api")
|
apiFile := c.String("api")
|
||||||
dir := c.String("dir")
|
dir := c.String("dir")
|
||||||
force := c.Bool("force")
|
namingStyle := c.String("style")
|
||||||
|
|
||||||
if len(apiFile) == 0 {
|
if len(apiFile) == 0 {
|
||||||
return errors.New("missing -api")
|
return errors.New("missing -api")
|
||||||
}
|
}
|
||||||
@@ -38,10 +37,10 @@ func GoCommand(c *cli.Context) error {
|
|||||||
return errors.New("missing -dir")
|
return errors.New("missing -dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoGenProject(apiFile, dir, force)
|
return DoGenProject(apiFile, dir, namingStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoGenProject(apiFile, dir string, force bool) error {
|
func DoGenProject(apiFile, dir, style string) error {
|
||||||
p, err := parser.NewParser(apiFile)
|
p, err := parser.NewParser(apiFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -51,22 +50,27 @@ func DoGenProject(apiFile, dir string, force bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg, err := config.NewConfig(style)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logx.Must(util.MkdirIfNotExist(dir))
|
logx.Must(util.MkdirIfNotExist(dir))
|
||||||
logx.Must(genEtc(dir, api))
|
logx.Must(genEtc(dir, cfg, api))
|
||||||
logx.Must(genConfig(dir, api))
|
logx.Must(genConfig(dir, cfg, api))
|
||||||
logx.Must(genMain(dir, api))
|
logx.Must(genMain(dir, cfg, api))
|
||||||
logx.Must(genServiceContext(dir, api))
|
logx.Must(genServiceContext(dir, cfg, api))
|
||||||
logx.Must(genTypes(dir, api, force))
|
logx.Must(genTypes(dir, cfg, api))
|
||||||
logx.Must(genHandlers(dir, api))
|
logx.Must(genRoutes(dir, cfg, api))
|
||||||
logx.Must(genRoutes(dir, api, force))
|
logx.Must(genHandlers(dir, cfg, api))
|
||||||
logx.Must(genLogic(dir, api))
|
logx.Must(genLogic(dir, cfg, api))
|
||||||
createGoModFileIfNeed(dir)
|
logx.Must(genMiddleware(dir, cfg, api))
|
||||||
|
|
||||||
if err := backupAndSweep(apiFile); err != nil {
|
if err := backupAndSweep(apiFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = apiformat.ApiFormat(apiFile, false); err != nil {
|
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,34 +133,3 @@ func sweep() error {
|
|||||||
return nil
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
604
tools/goctl/api/gogen/gen_test.go
Normal file
604
tools/goctl/api/gogen/gen_test.go
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
goformat "go/format"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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 = `
|
||||||
|
info(
|
||||||
|
title: doc title
|
||||||
|
desc: >
|
||||||
|
doc description first part,
|
||||||
|
doc description second part<
|
||||||
|
version: 1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// C2
|
||||||
|
@server( // C3
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response) // hello
|
||||||
|
|
||||||
|
// C4
|
||||||
|
@handler NoResponseHandler // C5
|
||||||
|
get /greet/get(Request)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testMultiServiceTemplate = `
|
||||||
|
info(
|
||||||
|
title: doc title
|
||||||
|
desc: >
|
||||||
|
doc description first part,
|
||||||
|
doc description second part<
|
||||||
|
version: 1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: NoResponseHandler
|
||||||
|
)
|
||||||
|
get /greet/get(Request) returns
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiNoInfo = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const invalidApiFile = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const anonymousAnnotation = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiHasMiddleware = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
middleware: TokenValidate
|
||||||
|
)
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiJwt = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
jwt: Auth
|
||||||
|
)
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiJwtWithMiddleware = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
jwt: Auth
|
||||||
|
middleware: TokenValidate
|
||||||
|
)
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiHasNoRequest = `
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
post /greet/ping ()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiRouteTest = `
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
|
}
|
||||||
|
service A-api {
|
||||||
|
@handler NormalHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
@handler NoResponseHandler
|
||||||
|
get /greet/from/:sex(Request)
|
||||||
|
@handler NoRequestHandler
|
||||||
|
get /greet/from/request returns (Response)
|
||||||
|
@handler NoRequestNoResponseHandler
|
||||||
|
get /greet/from
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const hasCommentApiTest = `
|
||||||
|
type Inline struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Inline
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // name in path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"msg"` + "`" + ` // message
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@doc(helloworld)
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const hasInlineNoExistTest = `
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Inline
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + ` // message
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@doc(helloworld)
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const importApi = `
|
||||||
|
type ImportData struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const hasImportApi = `
|
||||||
|
import "importApi.api"
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string ` + "`" + `json:"message"` + "`" + ` // message
|
||||||
|
}
|
||||||
|
|
||||||
|
service A-api {
|
||||||
|
@server(
|
||||||
|
handler: GreetHandler
|
||||||
|
)
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
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)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
api, err := parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, len(api.Types), 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()[1].RequestType.Name, "Request")
|
||||||
|
assert.Equal(t, api.Service.Routes()[1].ResponseType.Name, "")
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiService(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(testMultiServiceTemplate), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
api, err := parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, len(api.Service.Routes()), 2)
|
||||||
|
assert.Equal(t, len(api.Service.Groups), 2)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApiNoInfo(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiNoInfo), 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 TestInvalidApiFile(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(invalidApiFile), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
_, err = parser.NewParser(filename)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousAnnotation(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(anonymousAnnotation), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApiHasMiddleware(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiHasMiddleware), 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 TestApiHasJwt(t *testing.T) {
|
||||||
|
filename := "jwt.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiJwt), 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 TestApiHasJwtAndMiddleware(t *testing.T) {
|
||||||
|
filename := "jwt.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiJwtWithMiddleware), 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 TestApiHasNoRequestBody(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiHasNoRequest), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApiRoutes(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(apiRouteTest), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasCommentRoutes(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(hasCommentApiTest), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineTypeNotExist(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(hasInlineNoExistTest), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasImportApi(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(hasImportApi), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
|
||||||
|
importApiName := "importApi.api"
|
||||||
|
err = ioutil.WriteFile(importApiName, []byte(importApi), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(importApiName)
|
||||||
|
|
||||||
|
parser, err := parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
api, err := parser.Parse()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
var hasInline bool
|
||||||
|
for _, ty := range api.Types {
|
||||||
|
if ty.Name == "ImportData" {
|
||||||
|
hasInline = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, hasInline)
|
||||||
|
|
||||||
|
validate(t, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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 TestCamelStyle(t *testing.T) {
|
||||||
|
filename := "greet.api"
|
||||||
|
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(filename)
|
||||||
|
_, err = parser.NewParser(filename)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
validateWithCamel(t, filename, "GoZero")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(t *testing.T, api string) {
|
||||||
|
validateWithCamel(t, api, "gozero")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateWithCamel(t *testing.T, api, camel string) {
|
||||||
|
dir := "_go"
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
err := DoGenProject(api, dir, camel)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if strings.HasSuffix(path, ".go") {
|
||||||
|
code, err := ioutil.ReadFile(path)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, validateCode(string(code)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = execx.Run("go test ./...", dir)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCode(code string) error {
|
||||||
|
_, err := goformat.Source([]byte(code))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -8,12 +8,14 @@ import (
|
|||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
|
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configFile = "config.go"
|
configFile = "config"
|
||||||
configTemplate = `package config
|
configTemplate = `package config
|
||||||
|
|
||||||
import {{.authImport}}
|
import {{.authImport}}
|
||||||
@@ -31,8 +33,13 @@ type Config struct {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func genConfig(dir string, api *spec.ApiSpec) error {
|
func genConfig(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
fp, created, err := util.MaybeCreateFile(dir, configDir, configFile)
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, configFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, created, err := util.MaybeCreateFile(dir, configDir, filename+".go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -48,7 +55,7 @@ func genConfig(dir string, api *spec.ApiSpec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var authImportStr = fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
var authImportStr = fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||||
text, err := templatex.LoadTemplate(category, configTemplateFile, configTemplate)
|
text, err := ctlutil.LoadTemplate(category, configTemplateFile, configTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -60,8 +67,9 @@ func genConfig(dir string, api *spec.ApiSpec) error {
|
|||||||
"auth": strings.Join(auths, "\n"),
|
"auth": strings.Join(auths, "\n"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
|
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,8 +22,13 @@ Port: {{.port}}
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func genEtc(dir string, api *spec.ApiSpec) error {
|
func genEtc(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", api.Service.Name))
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, api.Service.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -31,16 +38,16 @@ func genEtc(dir string, api *spec.ApiSpec) error {
|
|||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
service := api.Service
|
service := api.Service
|
||||||
host, ok := util.GetAnnotationValue(service.Annotations, "server", "host")
|
host, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "host")
|
||||||
if !ok {
|
if !ok {
|
||||||
host = "0.0.0.0"
|
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 {
|
if !ok {
|
||||||
port = strconv.Itoa(defaultPort)
|
port = strconv.Itoa(defaultPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := templatex.LoadTemplate(category, etcTemplateFile, etcTemplate)
|
text, err := ctlutil.LoadTemplate(category, etcTemplateFile, etcTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -55,6 +62,7 @@ func genEtc(dir string, api *spec.ApiSpec) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ package gogen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,14 +27,14 @@ import (
|
|||||||
|
|
||||||
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.{{.RequestType}}
|
{{if .HasRequest}}var req types.{{.RequestType}}
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
httpx.Error(w, err)
|
httpx.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}{{end}}
|
||||||
|
|
||||||
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
||||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}(req)
|
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Error(w, err)
|
httpx.Error(w, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -48,15 +51,11 @@ type Handler struct {
|
|||||||
LogicType string
|
LogicType string
|
||||||
Call string
|
Call string
|
||||||
HasResp bool
|
HasResp bool
|
||||||
|
HasRequest bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
func genHandler(dir string, cfg *config.Config, group spec.Group, route spec.Route) error {
|
||||||
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
handler := getHandlerName(route)
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler = getHandlerName(handler)
|
|
||||||
if getHandlerFolderPath(group, route) != handlerDir {
|
if getHandlerFolderPath(group, route) != handlerDir {
|
||||||
handler = strings.Title(handler)
|
handler = strings.Title(handler)
|
||||||
}
|
}
|
||||||
@@ -65,26 +64,24 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return doGenToFile(dir, handler, group, route, Handler{
|
return doGenToFile(dir, handler, cfg, group, route, Handler{
|
||||||
ImportPackages: genHandlerImports(group, route, parentPkg),
|
ImportPackages: genHandlerImports(group, route, parentPkg),
|
||||||
HandlerName: handler,
|
HandlerName: handler,
|
||||||
RequestType: util.Title(route.RequestType.Name),
|
RequestType: util.Title(route.RequestType.Name),
|
||||||
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
LogicType: strings.Title(getLogicName(route)),
|
||||||
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||||
HasResp: len(route.ResponseType.Name) > 0,
|
HasResp: len(route.ResponseType.Name) > 0,
|
||||||
|
HasRequest: len(route.RequestType.Name) > 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handleObj Handler) error {
|
func doGenToFile(dir, handler string, cfg *config.Config, group spec.Group, route spec.Route, handleObj Handler) error {
|
||||||
if getHandlerFolderPath(group, route) != handlerDir {
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, handler)
|
||||||
handler = strings.Title(handler)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
filename := strings.ToLower(handler)
|
|
||||||
if strings.HasSuffix(filename, "handler") {
|
|
||||||
filename = filename + ".go"
|
|
||||||
} else {
|
|
||||||
filename = filename + "handler.go"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename = filename + ".go"
|
||||||
fp, created, err := apiutil.MaybeCreateFile(dir, getHandlerFolderPath(group, route), filename)
|
fp, created, err := apiutil.MaybeCreateFile(dir, getHandlerFolderPath(group, route), filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -94,7 +91,7 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
|||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
text, err := templatex.LoadTemplate(category, handlerTemplateFile, handlerTemplate)
|
text, err := util.LoadTemplate(category, handlerTemplateFile, handlerTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -102,7 +99,7 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
|||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
err = template.Must(template.New("handlerTemplate").Parse(text)).Execute(buffer, handleObj)
|
err = template.Must(template.New("handlerTemplate").Parse(text)).Execute(buffer, handleObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
@@ -110,10 +107,10 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func genHandlers(dir string, api *spec.ApiSpec) error {
|
func genHandlers(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
for _, group := range api.Service.Groups {
|
for _, group := range api.Service.Groups {
|
||||||
for _, route := range group.Routes {
|
for _, route := range group.Routes {
|
||||||
if err := genHandler(dir, group, route); err != nil {
|
if err := genHandler(dir, cfg, group, route); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,14 +132,23 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
|
|||||||
return strings.Join(imports, "\n\t")
|
return strings.Join(imports, "\n\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHandlerBaseName(handler string) string {
|
func getHandlerBaseName(route spec.Route) (string, error) {
|
||||||
handlerName := util.Untitle(handler)
|
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||||
if strings.HasSuffix(handlerName, "handler") {
|
if !ok {
|
||||||
handlerName = strings.ReplaceAll(handlerName, "handler", "")
|
return "", fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||||
} else if strings.HasSuffix(handlerName, "Handler") {
|
|
||||||
handlerName = strings.ReplaceAll(handlerName, "Handler", "")
|
|
||||||
}
|
}
|
||||||
return handlerName
|
|
||||||
|
for _, char := range handler {
|
||||||
|
if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
|
||||||
|
return "", errors.New(fmt.Sprintf("route [%s] handler [%s] invalid, handler name should only contains letter or digit",
|
||||||
|
route.Path, handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = strings.TrimSpace(handler)
|
||||||
|
handler = strings.TrimSuffix(handler, "handler")
|
||||||
|
handler = strings.TrimSuffix(handler, "Handler")
|
||||||
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
||||||
@@ -158,6 +164,20 @@ func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
|||||||
return path.Join(handlerDir, folder)
|
return path.Join(handlerDir, folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHandlerName(handler string) string {
|
func getHandlerName(route spec.Route) string {
|
||||||
return getHandlerBaseName(handler) + "Handler"
|
handler, err := getHandlerBaseName(route)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler + "Handler"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogicName(route spec.Route) string {
|
||||||
|
handler, err := getHandlerBaseName(route)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler + "Logic"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import (
|
|||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,10 +42,10 @@ func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func genLogic(dir string, api *spec.ApiSpec) error {
|
func genLogic(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
for _, g := range api.Service.Groups {
|
for _, g := range api.Service.Groups {
|
||||||
for _, r := range g.Routes {
|
for _, r := range g.Routes {
|
||||||
err := genLogicByRoute(dir, g, r)
|
err := genLogicByRoute(dir, cfg, g, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -53,16 +54,14 @@ func genLogic(dir string, api *spec.ApiSpec) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spec.Route) error {
|
||||||
handler, ok := util.GetAnnotationValue(route.Annotations, "server", "handler")
|
logic := getLogicName(route)
|
||||||
if !ok {
|
goFile, err := format.FileNamingFormat(cfg.NamingFormat, logic)
|
||||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = strings.TrimSuffix(handler, "handler")
|
goFile = goFile + ".go"
|
||||||
handler = strings.TrimSuffix(handler, "Handler")
|
|
||||||
filename := strings.ToLower(handler)
|
|
||||||
goFile := filename + "logic.go"
|
|
||||||
fp, created, err := util.MaybeCreateFile(dir, getLogicFolderPath(group, route), goFile)
|
fp, created, err := util.MaybeCreateFile(dir, getLogicFolderPath(group, route), goFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -94,7 +93,7 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
|||||||
requestString = "req " + "types." + strings.Title(route.RequestType.Name)
|
requestString = "req " + "types." + strings.Title(route.RequestType.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := templatex.LoadTemplate(category, logicTemplateFile, logicTemplate)
|
text, err := ctlutil.LoadTemplate(category, logicTemplateFile, logicTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,8 +102,8 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
|||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
err = t.Execute(fp, map[string]string{
|
err = t.Execute(fp, map[string]string{
|
||||||
"imports": imports,
|
"imports": imports,
|
||||||
"logic": strings.Title(handler) + "Logic",
|
"logic": strings.Title(logic),
|
||||||
"function": strings.Title(strings.TrimSuffix(handler, "Handler")),
|
"function": strings.Title(strings.TrimSuffix(logic, "Logic")),
|
||||||
"responseType": responseString,
|
"responseType": responseString,
|
||||||
"returnString": returnString,
|
"returnString": returnString,
|
||||||
"request": requestString,
|
"request": requestString,
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,12 +42,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func genMain(dir string, api *spec.ApiSpec) error {
|
func genMain(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
name := strings.ToLower(api.Service.Name)
|
name := strings.ToLower(api.Service.Name)
|
||||||
if strings.HasSuffix(name, "-api") {
|
if strings.HasSuffix(name, "-api") {
|
||||||
name = strings.ReplaceAll(name, "-api", "")
|
name = strings.ReplaceAll(name, "-api", "")
|
||||||
}
|
}
|
||||||
goFile := name + ".go"
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
goFile := filename + ".go"
|
||||||
fp, created, err := util.MaybeCreateFile(dir, "", goFile)
|
fp, created, err := util.MaybeCreateFile(dir, "", goFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -61,7 +67,7 @@ func genMain(dir string, api *spec.ApiSpec) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := templatex.LoadTemplate(category, mainTemplateFile, mainTemplate)
|
text, err := ctlutil.LoadTemplate(category, mainTemplateFile, mainTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -73,8 +79,9 @@ func genMain(dir string, api *spec.ApiSpec) error {
|
|||||||
"serviceName": api.Service.Name,
|
"serviceName": api.Service.Name,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
|
|||||||
70
tools/goctl/api/gogen/genmiddleware.go
Normal file
70
tools/goctl/api/gogen/genmiddleware.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var middlewareImplementCode = `
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type {{.name}} struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{.name}}() *{{.name}} {
|
||||||
|
return &{{.name}}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO generate middleware implement function, delete after code implementation
|
||||||
|
|
||||||
|
// Passthrough to next handler if need
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func genMiddleware(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
|
var middlewares = getMiddleware(api)
|
||||||
|
for _, item := range middlewares {
|
||||||
|
middlewareFilename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "_middleware"
|
||||||
|
formatName, err := format.FileNamingFormat(cfg.NamingFormat, middlewareFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := formatName + ".go"
|
||||||
|
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !created {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||||
|
t := template.Must(template.New("contextTemplate").Parse(middlewareImplementCode))
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
err = t.Execute(buffer, map[string]string{
|
||||||
|
"name": strings.Title(name),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCode := formatCode(buffer.String())
|
||||||
|
_, err = fp.WriteString(formatCode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package gogen
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,12 +12,14 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/collection"
|
"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/spec"
|
||||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
routesFilename = "routes.go"
|
routesFilename = "routes"
|
||||||
routesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
routesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
@@ -31,9 +34,9 @@ func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
routesAdditionTemplate = `
|
routesAdditionTemplate = `
|
||||||
engine.AddRoutes([]rest.Route{
|
engine.AddRoutes(
|
||||||
{{.routes}}
|
{{.routes}} {{.jwt}}{{.signature}}
|
||||||
}{{.jwt}}{{.signature}})
|
)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ type (
|
|||||||
jwtEnabled bool
|
jwtEnabled bool
|
||||||
signatureEnabled bool
|
signatureEnabled bool
|
||||||
authName string
|
authName string
|
||||||
|
middlewares []string
|
||||||
}
|
}
|
||||||
route struct {
|
route struct {
|
||||||
method string
|
method string
|
||||||
@@ -60,7 +64,7 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
func genRoutes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
groups, err := getRoutes(api)
|
groups, err := getRoutes(api)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,6 +74,7 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
|||||||
gt := template.Must(template.New("groupTemplate").Parse(routesAdditionTemplate))
|
gt := template.Must(template.New("groupTemplate").Parse(routesAdditionTemplate))
|
||||||
for _, g := range groups {
|
for _, g := range groups {
|
||||||
var gbuilder strings.Builder
|
var gbuilder strings.Builder
|
||||||
|
gbuilder.WriteString("[]rest.Route{")
|
||||||
for _, r := range g.routes {
|
for _, r := range g.routes {
|
||||||
fmt.Fprintf(&gbuilder, `
|
fmt.Fprintf(&gbuilder, `
|
||||||
{
|
{
|
||||||
@@ -79,16 +84,33 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
|||||||
},`,
|
},`,
|
||||||
r.method, r.path, r.handler)
|
r.method, r.path, r.handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
var jwt string
|
var jwt string
|
||||||
if g.jwtEnabled {
|
if g.jwtEnabled {
|
||||||
jwt = fmt.Sprintf(", rest.WithJwt(serverCtx.Config.%s.AccessSecret)", g.authName)
|
jwt = fmt.Sprintf("\n rest.WithJwt(serverCtx.Config.%s.AccessSecret),", g.authName)
|
||||||
}
|
}
|
||||||
var signature string
|
var signature string
|
||||||
if g.signatureEnabled {
|
if g.signatureEnabled {
|
||||||
signature = fmt.Sprintf(", rest.WithSignature(serverCtx.Config.%s.Signature)", g.authName)
|
signature = fmt.Sprintf("\n rest.WithSignature(serverCtx.Config.%s.Signature),", g.authName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var routes string
|
||||||
|
if len(g.middlewares) > 0 {
|
||||||
|
gbuilder.WriteString("\n}...,")
|
||||||
|
var params = g.middlewares
|
||||||
|
for i := range params {
|
||||||
|
params[i] = "serverCtx." + params[i]
|
||||||
|
}
|
||||||
|
var middlewareStr = strings.Join(params, ", ")
|
||||||
|
routes = fmt.Sprintf("rest.WithMiddlewares(\n[]rest.Middleware{ %s }, \n %s \n),",
|
||||||
|
middlewareStr, strings.TrimSpace(gbuilder.String()))
|
||||||
|
} else {
|
||||||
|
gbuilder.WriteString("\n},")
|
||||||
|
routes = strings.TrimSpace(gbuilder.String())
|
||||||
|
}
|
||||||
|
|
||||||
if err := gt.Execute(&builder, map[string]string{
|
if err := gt.Execute(&builder, map[string]string{
|
||||||
"routes": strings.TrimSpace(gbuilder.String()),
|
"routes": routes,
|
||||||
"jwt": jwt,
|
"jwt": jwt,
|
||||||
"signature": signature,
|
"signature": signature,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -101,14 +123,16 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := path.Join(dir, handlerDir, routesFilename)
|
routeFilename, err := format.FileNamingFormat(cfg.NamingFormat, routesFilename)
|
||||||
if !force {
|
if err != nil {
|
||||||
if err := util.RemoveOrQuit(filename); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
routeFilename = routeFilename + ".go"
|
||||||
|
|
||||||
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routesFilename)
|
filename := path.Join(dir, handlerDir, routeFilename)
|
||||||
|
os.Remove(filename)
|
||||||
|
|
||||||
|
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routeFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -124,8 +148,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
|||||||
"routesAdditions": strings.TrimSpace(builder.String()),
|
"routesAdditions": strings.TrimSpace(builder.String()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
@@ -143,8 +168,7 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
importSet.AddStr(fmt.Sprintf("%s \"%s\"", folder,
|
importSet.AddStr(fmt.Sprintf("%s \"%s\"", toPrefix(folder), util.JoinPackages(parentPkg, handlerDir, folder)))
|
||||||
util.JoinPackages(parentPkg, handlerDir, folder)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imports := importSet.KeysStr()
|
imports := importSet.KeysStr()
|
||||||
@@ -160,18 +184,15 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
|||||||
for _, g := range api.Service.Groups {
|
for _, g := range api.Service.Groups {
|
||||||
var groupedRoutes group
|
var groupedRoutes group
|
||||||
for _, r := range g.Routes {
|
for _, r := range g.Routes {
|
||||||
handler, ok := apiutil.GetAnnotationValue(r.Annotations, "server", "handler")
|
handler := getHandlerName(r)
|
||||||
if !ok {
|
handler = handler + "(serverCtx)"
|
||||||
return nil, fmt.Errorf("missing handler annotation for route %q", r.Path)
|
|
||||||
}
|
|
||||||
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
|
|
||||||
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
|
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
|
||||||
if ok {
|
if ok {
|
||||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||||
} else {
|
} else {
|
||||||
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
|
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
|
||||||
if ok {
|
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{
|
groupedRoutes.routes = append(groupedRoutes.routes, route{
|
||||||
@@ -185,8 +206,17 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
|||||||
groupedRoutes.authName = value
|
groupedRoutes.authName = value
|
||||||
groupedRoutes.jwtEnabled = true
|
groupedRoutes.jwtEnabled = true
|
||||||
}
|
}
|
||||||
|
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
|
||||||
|
for _, item := range strings.Split(value, ",") {
|
||||||
|
groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
routes = append(routes, groupedRoutes)
|
routes = append(routes, groupedRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes, nil
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPrefix(folder string) string {
|
||||||
|
return strings.ReplaceAll(folder, "/", "")
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,32 +3,47 @@ package gogen
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
contextFilename = "servicecontext.go"
|
contextFilename = "service_context"
|
||||||
contextTemplate = `package svc
|
contextTemplate = `package svc
|
||||||
|
|
||||||
import {{.configImport}}
|
import (
|
||||||
|
{{.configImport}}
|
||||||
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config {{.config}}
|
Config {{.config}}
|
||||||
|
{{.middleware}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c {{.config}}) *ServiceContext {
|
func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||||
return &ServiceContext{Config: c}
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
{{.middlewareAssignment}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func genServiceContext(dir string, api *spec.ApiSpec) error {
|
func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
fp, created, err := util.MaybeCreateFile(dir, contextDir, contextFilename)
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, contextFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, created, err := util.MaybeCreateFile(dir, contextDir, filename+".go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -48,21 +63,39 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := templatex.LoadTemplate(category, contextTemplateFile, contextTemplate)
|
text, err := ctlutil.LoadTemplate(category, contextTemplateFile, contextTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var middlewareStr string
|
||||||
|
var middlewareAssignment string
|
||||||
|
var middlewares = getMiddleware(api)
|
||||||
|
|
||||||
|
for _, item := range middlewares {
|
||||||
|
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
|
||||||
|
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||||
|
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item, fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
|
||||||
|
}
|
||||||
|
|
||||||
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
||||||
|
if len(middlewareStr) > 0 {
|
||||||
|
configImport += "\n\t\"" + ctlutil.JoinPackages(parentPkg, middlewareDir) + "\""
|
||||||
|
configImport += fmt.Sprintf("\n\t\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||||
|
}
|
||||||
|
|
||||||
t := template.Must(template.New("contextTemplate").Parse(text))
|
t := template.Must(template.New("contextTemplate").Parse(text))
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
err = t.Execute(buffer, map[string]string{
|
err = t.Execute(buffer, map[string]string{
|
||||||
"configImport": configImport,
|
"configImport": configImport,
|
||||||
"config": "config.Config",
|
"config": "config.Config",
|
||||||
|
"middleware": middlewareStr,
|
||||||
|
"middlewareAssignment": middlewareAssignment,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,17 +5,20 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
typesFile = "types.go"
|
typesFile = "types"
|
||||||
typesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
typesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
||||||
package types{{if .containsTime}}
|
package types{{if .containsTime}}
|
||||||
import (
|
import (
|
||||||
@@ -42,23 +45,25 @@ func BuildTypes(types []spec.Type) (string, error) {
|
|||||||
return builder.String(), nil
|
return builder.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func genTypes(dir string, api *spec.ApiSpec, force bool) error {
|
func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
val, err := BuildTypes(api.Types)
|
val, err := BuildTypes(api.Types)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := path.Join(dir, typesDir, typesFile)
|
typeFilename, err := format.FileNamingFormat(cfg.NamingFormat, typesFile)
|
||||||
if !force {
|
|
||||||
if err := util.RemoveOrQuit(filename); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typesFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
typeFilename = typeFilename + ".go"
|
||||||
|
filename := path.Join(dir, typesDir, typeFilename)
|
||||||
|
os.Remove(filename)
|
||||||
|
|
||||||
|
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typeFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if !created {
|
if !created {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -71,8 +76,9 @@ func genTypes(dir string, api *spec.ApiSpec, force bool) error {
|
|||||||
"containsTime": api.ContainsTime(),
|
"containsTime": api.ContainsTime(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
formatCode := formatCode(buffer.String())
|
formatCode := formatCode(buffer.String())
|
||||||
_, err = fp.WriteString(formatCode)
|
_, err = fp.WriteString(formatCode)
|
||||||
return err
|
return err
|
||||||
@@ -90,12 +96,6 @@ func convertTypeCase(types []spec.Type, t string) (string, error) {
|
|||||||
if typ.Name == tp {
|
if typ.Name == tp {
|
||||||
defTypes = append(defTypes, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package gogen
|
package gogen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,5 +27,29 @@ var templates = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenTemplates(_ *cli.Context) error {
|
func GenTemplates(_ *cli.Context) error {
|
||||||
return templatex.InitTemplates(category, templates)
|
return util.InitTemplates(category, templates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RevertTemplate(name string) error {
|
||||||
|
content, ok := templates[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%s: no such file name", name)
|
||||||
|
}
|
||||||
|
return util.CreateTemplate(category, name, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Update(category string) error {
|
||||||
|
err := Clean()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return util.InitTemplates(category, templates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clean() error {
|
||||||
|
return util.Clean(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCategory() string {
|
||||||
|
return category
|
||||||
}
|
}
|
||||||
|
|||||||
92
tools/goctl/api/gogen/template_test.go
Normal file
92
tools/goctl/api/gogen/template_test.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenTemplates(t *testing.T) {
|
||||||
|
err := util.InitTemplates(category, templates)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
dir, err := util.GetTemplateDir(category)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
file := filepath.Join(dir, "main.tpl")
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, string(data), mainTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRevertTemplate(t *testing.T) {
|
||||||
|
name := "main.tpl"
|
||||||
|
err := util.InitTemplates(category, templates)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
dir, err := util.GetTemplateDir(category)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
file := filepath.Join(dir, name)
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
modifyData := string(data) + "modify"
|
||||||
|
err = util.CreateTemplate(category, name, modifyData)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
data, err = ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(data), modifyData)
|
||||||
|
|
||||||
|
assert.Nil(t, RevertTemplate(name))
|
||||||
|
|
||||||
|
data, err = ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, mainTemplate, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClean(t *testing.T) {
|
||||||
|
name := "main.tpl"
|
||||||
|
err := util.InitTemplates(category, templates)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, Clean())
|
||||||
|
|
||||||
|
dir, err := util.GetTemplateDir(category)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
file := filepath.Join(dir, name)
|
||||||
|
_, err = ioutil.ReadFile(file)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
name := "main.tpl"
|
||||||
|
err := util.InitTemplates(category, templates)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
dir, err := util.GetTemplateDir(category)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
file := filepath.Join(dir, name)
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
modifyData := string(data) + "modify"
|
||||||
|
err = util.CreateTemplate(category, name, modifyData)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
data, err = ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(data), modifyData)
|
||||||
|
|
||||||
|
assert.Nil(t, Update(category))
|
||||||
|
|
||||||
|
data, err = ioutil.ReadFile(file)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, mainTemplate, string(data))
|
||||||
|
}
|
||||||
@@ -10,38 +10,24 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/collection"
|
"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/spec"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
"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/ctx"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getParentPackage(dir string) (string, error) {
|
func getParentPackage(dir string) (string, error) {
|
||||||
p, err := project.Prepare(dir, false)
|
abs, err := filepath.Abs(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.GoMod.Path) > 0 {
|
projectCtx, err := ctx.Prepare(abs)
|
||||||
goModePath := filepath.Clean(filepath.Dir(p.GoMod.Path))
|
if err != nil {
|
||||||
absPath, err := filepath.Abs(dir)
|
return "", err
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
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 {
|
func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
|
||||||
writeIndent(writer, indent)
|
util.WriteIndent(writer, indent)
|
||||||
var err error
|
var err error
|
||||||
if len(comment) > 0 {
|
if len(comment) > 0 {
|
||||||
comment = strings.TrimPrefix(comment, "//")
|
comment = strings.TrimPrefix(comment, "//")
|
||||||
@@ -66,6 +52,18 @@ func getAuths(api *spec.ApiSpec) []string {
|
|||||||
return authNames.KeysStr()
|
return authNames.KeysStr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMiddleware(api *spec.ApiSpec) []string {
|
||||||
|
result := collection.NewSet()
|
||||||
|
for _, g := range api.Service.Groups {
|
||||||
|
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
|
||||||
|
for _, item := range strings.Split(value, ",") {
|
||||||
|
result.Add(strings.TrimSpace(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.KeysStr()
|
||||||
|
}
|
||||||
|
|
||||||
func formatCode(code string) string {
|
func formatCode(code string) string {
|
||||||
ret, err := goformat.Source([]byte(code))
|
ret, err := goformat.Source([]byte(code))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const (
|
|||||||
contextDir = interval + "svc"
|
contextDir = interval + "svc"
|
||||||
handlerDir = interval + "handler"
|
handlerDir = interval + "handler"
|
||||||
logicDir = interval + "logic"
|
logicDir = interval + "logic"
|
||||||
|
middlewareDir = interval + "middleware"
|
||||||
typesDir = interval + typesPacket
|
typesDir = interval + typesPacket
|
||||||
groupProperty = "group"
|
groupProperty = "group"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packet
|
|||||||
`
|
`
|
||||||
|
|
||||||
func genPacket(dir, packetName string, api *spec.ApiSpec) error {
|
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 {
|
if err := createWith(dir, api, route, packetName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,33 +3,35 @@ package new
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
||||||
|
conf "github.com/tal-tech/go-zero/tools/goctl/config"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiTemplate = `
|
const apiTemplate = `
|
||||||
type Request struct {
|
type Request {
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法
|
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response {
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
Message string ` + "`" + `json:"message"` + "`" + `
|
||||||
}
|
}
|
||||||
|
|
||||||
service {{.name}}-api {
|
service {{.name}}-api {
|
||||||
@handler GreetHandler
|
@handler {{.handler}}Handler
|
||||||
get /greet/from/:name(Request) returns (Response);
|
get /from/:name(Request) returns (Response);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func NewService(c *cli.Context) error {
|
func NewService(c *cli.Context) error {
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
dirName := "greet"
|
dirName := args.First()
|
||||||
if len(args) > 0 {
|
if len(dirName) == 0 {
|
||||||
dirName = args.First()
|
dirName = "greet"
|
||||||
}
|
}
|
||||||
|
|
||||||
abs, err := filepath.Abs(dirName)
|
abs, err := filepath.Abs(dirName)
|
||||||
@@ -53,11 +55,12 @@ func NewService(c *cli.Context) error {
|
|||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
t := template.Must(template.New("template").Parse(apiTemplate))
|
t := template.Must(template.New("template").Parse(apiTemplate))
|
||||||
if err := t.Execute(fp, map[string]string{
|
if err := t.Execute(fp, map[string]string{
|
||||||
"name": dirName,
|
"name": dirName,
|
||||||
|
"handler": strings.Title(dirName),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gogen.DoGenProject(apiFilePath, abs, true)
|
err = gogen.DoGenProject(apiFilePath, abs, conf.DefaultFormat)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
268
tools/goctl/api/parser/apifileparser.go
Normal file
268
tools/goctl/api/parser/apifileparser.go
Normal 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, " ")
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ func (s *baseState) parseProperties() (map[string]string, error) {
|
|||||||
var st = startState
|
var st = startState
|
||||||
|
|
||||||
for {
|
for {
|
||||||
ch, err := s.read()
|
ch, err := s.readSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -164,6 +164,60 @@ func (s *baseState) read() (rune, error) {
|
|||||||
return value, nil
|
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) {
|
func (s *baseState) readLine() (string, error) {
|
||||||
line, _, err := s.r.ReadLine()
|
line, _, err := s.r.ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *entity) process() error {
|
func (s *entity) process() error {
|
||||||
line, err := s.state.readLine()
|
line, err := s.state.readLineSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ func (s *entity) process() error {
|
|||||||
var annos []spec.Annotation
|
var annos []spec.Annotation
|
||||||
memberLoop:
|
memberLoop:
|
||||||
for {
|
for {
|
||||||
ch, err := s.state.read()
|
ch, err := s.state.readSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ memberLoop:
|
|||||||
case ch == at:
|
case ch == at:
|
||||||
annotationLoop:
|
annotationLoop:
|
||||||
for {
|
for {
|
||||||
next, err := s.state.read()
|
next, err := s.state.readSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case isSpace(next):
|
case isSpace(next):
|
||||||
if builder.Len() > 0 {
|
if builder.Len() > 0 && annoName == "" {
|
||||||
annoName = builder.String()
|
annoName = builder.String()
|
||||||
builder.Reset()
|
builder.Reset()
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,7 @@ memberLoop:
|
|||||||
if builder.Len() == 0 {
|
if builder.Len() == 0 {
|
||||||
return errors.New("invalid annotation format")
|
return errors.New("invalid annotation format")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(annoName) > 0 {
|
if len(annoName) > 0 {
|
||||||
value := builder.String()
|
value := builder.String()
|
||||||
if value != string(leftParenthesis) {
|
if value != string(leftParenthesis) {
|
||||||
@@ -127,7 +128,7 @@ memberLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var line string
|
var line string
|
||||||
line, err = s.state.readLine()
|
line, err = s.state.readLineSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -14,9 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
typeDef string
|
api *ApiStruct
|
||||||
api *ApiStruct
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser(filename string) (*Parser, error) {
|
func NewParser(filename string) (*Parser, error) {
|
||||||
@@ -34,39 +34,57 @@ func NewParser(filename string) (*Parser, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range strings.Split(apiStruct.Imports, "\n") {
|
for _, item := range strings.Split(apiStruct.Imports, "\n") {
|
||||||
ip := strings.TrimSpace(item)
|
importLine := strings.TrimSpace(item)
|
||||||
if len(ip) > 0 {
|
if len(importLine) > 0 {
|
||||||
item := strings.TrimPrefix(item, "import")
|
item := strings.TrimPrefix(importLine, "import")
|
||||||
item = strings.TrimSpace(item)
|
item = strings.TrimSpace(item)
|
||||||
|
item = strings.TrimPrefix(item, `"`)
|
||||||
|
item = strings.TrimSuffix(item, `"`)
|
||||||
var path = item
|
var path = item
|
||||||
if !util.FileExists(item) {
|
if !util.FileExists(item) {
|
||||||
path = filepath.Join(filepath.Dir(apiAbsPath), item)
|
path = filepath.Join(filepath.Dir(apiAbsPath), item)
|
||||||
}
|
}
|
||||||
content, err := ioutil.ReadFile(path)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
var buffer = new(bytes.Buffer)
|
||||||
buffer.WriteString(apiStruct.Service)
|
buffer.WriteString(apiStruct.Service)
|
||||||
return &Parser{
|
return &Parser{
|
||||||
r: bufio.NewReader(buffer),
|
r: bufio.NewReader(buffer),
|
||||||
typeDef: apiStruct.StructBody,
|
api: apiStruct,
|
||||||
api: apiStruct,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
|
func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
|
||||||
api = new(spec.ApiSpec)
|
api = new(spec.ApiSpec)
|
||||||
var sp = StructParser{Src: p.typeDef}
|
var sp = StructParser{Src: p.api.Type}
|
||||||
types, err := sp.Parse()
|
types, err := sp.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
api.Types = types
|
api.Types = types
|
||||||
var lineNumber = p.api.serviceBeginLine
|
var lineNumber = p.api.serviceBeginLine
|
||||||
st := newRootState(p.r, &lineNumber)
|
st := newRootState(p.r, &lineNumber)
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testApiTemplate = `
|
|
||||||
info(
|
|
||||||
title: doc title
|
|
||||||
desc: >
|
|
||||||
doc description first part,
|
|
||||||
doc description second part<
|
|
||||||
version: 1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api {
|
|
||||||
@server(
|
|
||||||
handler: GreetHandler
|
|
||||||
)
|
|
||||||
get /greet/from/:name(Request) returns (Response)
|
|
||||||
|
|
||||||
@server(
|
|
||||||
handler: NoResponseHandler
|
|
||||||
)
|
|
||||||
get /greet/get(Request) returns
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const testMultiServiceTemplate = `
|
|
||||||
info(
|
|
||||||
title: doc title
|
|
||||||
desc: >
|
|
||||||
doc description first part,
|
|
||||||
doc description second part<
|
|
||||||
version: 1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api {
|
|
||||||
@server(
|
|
||||||
handler: GreetHandler
|
|
||||||
)
|
|
||||||
get /greet/from/:name(Request) returns (Response)
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api {
|
|
||||||
@server(
|
|
||||||
handler: NoResponseHandler
|
|
||||||
)
|
|
||||||
get /greet/get(Request) returns
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const apiNoInfo = `
|
|
||||||
type Request struct {
|
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api {
|
|
||||||
@server(
|
|
||||||
handler: GreetHandler
|
|
||||||
)
|
|
||||||
get /greet/from/:name(Request) returns (Response)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const invalidApiFile = `
|
|
||||||
type Request struct {
|
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api
|
|
||||||
@server(
|
|
||||||
handler: GreetHandler
|
|
||||||
)
|
|
||||||
get /greet/from/:name(Request) returns (Response)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const anonymousAnnotation = `
|
|
||||||
type Request struct {
|
|
||||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Message string ` + "`" + `json:"message"` + "`" + `
|
|
||||||
}
|
|
||||||
|
|
||||||
service A-api {
|
|
||||||
@handler GreetHandler
|
|
||||||
get /greet/from/:name(Request) returns (Response)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
|
||||||
filename := "greet.api"
|
|
||||||
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer os.Remove(filename)
|
|
||||||
|
|
||||||
parser, err := NewParser(filename)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
api, err := parser.Parse()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, len(api.Types), 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[1].RequestType.Name, "Request")
|
|
||||||
assert.Equal(t, api.Service.Routes[1].ResponseType.Name, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiService(t *testing.T) {
|
|
||||||
filename := "greet.api"
|
|
||||||
err := ioutil.WriteFile(filename, []byte(testMultiServiceTemplate), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer os.Remove(filename)
|
|
||||||
|
|
||||||
parser, err := NewParser(filename)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
api, err := parser.Parse()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, len(api.Service.Routes), 2)
|
|
||||||
assert.Equal(t, len(api.Service.Groups), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApiNoInfo(t *testing.T) {
|
|
||||||
filename := "greet.api"
|
|
||||||
err := ioutil.WriteFile(filename, []byte(apiNoInfo), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer os.Remove(filename)
|
|
||||||
|
|
||||||
parser, err := NewParser(filename)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
_, err = parser.Parse()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidApiFile(t *testing.T) {
|
|
||||||
filename := "greet.api"
|
|
||||||
err := ioutil.WriteFile(filename, []byte(invalidApiFile), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer os.Remove(filename)
|
|
||||||
|
|
||||||
parser, err := NewParser(filename)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
_, err = parser.Parse()
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnonymousAnnotation(t *testing.T) {
|
|
||||||
filename := "greet.api"
|
|
||||||
err := ioutil.WriteFile(filename, []byte(anonymousAnnotation), os.ModePerm)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer os.Remove(filename)
|
|
||||||
|
|
||||||
parser, err := NewParser(filename)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
var annos []spec.Annotation
|
var annos []spec.Annotation
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for {
|
for {
|
||||||
ch, err := s.read()
|
ch, err := s.readSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
if builder.Len() == 0 {
|
if builder.Len() == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
token := builder.String()
|
token := builder.String()
|
||||||
builder.Reset()
|
builder.Reset()
|
||||||
return s.processToken(token, annos)
|
return s.processToken(token, annos)
|
||||||
@@ -44,10 +45,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
var annoName string
|
var annoName string
|
||||||
annoLoop:
|
annoLoop:
|
||||||
for {
|
for {
|
||||||
next, err := s.read()
|
next, err := s.readSkipComment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case isSpace(next):
|
case isSpace(next):
|
||||||
if builder.Len() > 0 {
|
if builder.Len() > 0 {
|
||||||
@@ -58,6 +60,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
if err := s.unread(); err != nil {
|
if err := s.unread(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if builder.Len() > 0 {
|
if builder.Len() > 0 {
|
||||||
annoName = builder.String()
|
annoName = builder.String()
|
||||||
builder.Reset()
|
builder.Reset()
|
||||||
@@ -66,6 +69,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
annos = append(annos, spec.Annotation{
|
annos = append(annos, spec.Annotation{
|
||||||
Name: annoName,
|
Name: annoName,
|
||||||
Properties: attrs,
|
Properties: attrs,
|
||||||
@@ -79,9 +83,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
if builder.Len() == 0 {
|
if builder.Len() == 0 {
|
||||||
return nil, fmt.Errorf("incorrect %q at the beginning of the line", leftParenthesis)
|
return nil, fmt.Errorf("incorrect %q at the beginning of the line", leftParenthesis)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.unread(); err != nil {
|
if err := s.unread(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token := builder.String()
|
token := builder.String()
|
||||||
builder.Reset()
|
builder.Reset()
|
||||||
return s.processToken(token, annos)
|
return s.processToken(token, annos)
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ func (s *serviceState) process(api *spec.ApiSpec) (state, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.Service = spec.Service{
|
api.Service = spec.Service{
|
||||||
Name: name,
|
Name: name,
|
||||||
Annotations: append(api.Service.Annotations, s.annos...),
|
|
||||||
Routes: append(api.Service.Routes, routes...),
|
|
||||||
Groups: append(api.Service.Groups, spec.Group{
|
Groups: append(api.Service.Groups, spec.Group{
|
||||||
Annotations: s.annos,
|
Annotations: s.annos,
|
||||||
Routes: routes,
|
Routes: routes,
|
||||||
@@ -70,6 +68,12 @@ func (p *serviceEntityParser) parseLine(line string, api *spec.ApiSpec, annos []
|
|||||||
ch, _, err := reader.ReadRune()
|
ch, _, err := reader.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
if builder.Len() > 0 {
|
||||||
|
token := strings.TrimSpace(builder.String())
|
||||||
|
if len(token) > 0 && token != returnsTag {
|
||||||
|
fields = append(fields, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type typeState struct {
|
|
||||||
*baseState
|
|
||||||
annos []spec.Annotation
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTypeState(state *baseState, annos []spec.Annotation) state {
|
|
||||||
return &typeState{
|
|
||||||
baseState: state,
|
|
||||||
annos: annos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *typeState) process(api *spec.ApiSpec) (state, error) {
|
|
||||||
var name string
|
|
||||||
var members []spec.Member
|
|
||||||
parser := &typeEntityParser{
|
|
||||||
acceptName: func(n string) {
|
|
||||||
name = n
|
|
||||||
},
|
|
||||||
acceptMember: func(member spec.Member) {
|
|
||||||
members = append(members, member)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ent := newEntity(s.baseState, api, parser)
|
|
||||||
if err := ent.process(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
api.Types = append(api.Types, spec.Type{
|
|
||||||
Name: name,
|
|
||||||
Annotations: s.annos,
|
|
||||||
Members: members,
|
|
||||||
})
|
|
||||||
|
|
||||||
return newRootState(s.r, s.lineNumber), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type typeEntityParser struct {
|
|
||||||
acceptName func(name string)
|
|
||||||
acceptMember func(member spec.Member)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *typeEntityParser) parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error {
|
|
||||||
index := strings.Index(line, "//")
|
|
||||||
comment := ""
|
|
||||||
if index >= 0 {
|
|
||||||
comment = line[index+2:]
|
|
||||||
line = strings.TrimSpace(line[:index])
|
|
||||||
}
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(fields) == 1 {
|
|
||||||
p.acceptMember(spec.Member{
|
|
||||||
Annotations: annos,
|
|
||||||
Name: fields[0],
|
|
||||||
Type: fields[0],
|
|
||||||
IsInline: true,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name := fields[0]
|
|
||||||
tp := fields[1]
|
|
||||||
var tag string
|
|
||||||
if len(fields) > 2 {
|
|
||||||
tag = fields[2]
|
|
||||||
} else {
|
|
||||||
tag = fmt.Sprintf("`json:\"%s\"`", util.Untitle(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
p.acceptMember(spec.Member{
|
|
||||||
Annotations: annos,
|
|
||||||
Name: name,
|
|
||||||
Type: tp,
|
|
||||||
Tag: tag,
|
|
||||||
Comment: comment,
|
|
||||||
IsInline: false,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *typeEntityParser) setEntityName(name string) {
|
|
||||||
p.acceptName(name)
|
|
||||||
}
|
|
||||||
@@ -2,22 +2,12 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyType spec.Type
|
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 {
|
func GetType(api *spec.ApiSpec, t string) spec.Type {
|
||||||
for _, tp := range api.Types {
|
for _, tp := range api.Types {
|
||||||
if tp.Name == t {
|
if tp.Name == t {
|
||||||
@@ -36,6 +26,10 @@ func isSpace(r rune) bool {
|
|||||||
return r == ' ' || r == '\t'
|
return r == ' ' || r == '\t'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSlash(r rune) bool {
|
||||||
|
return r == '/'
|
||||||
|
}
|
||||||
|
|
||||||
func isNewline(r rune) bool {
|
func isNewline(r rune) bool {
|
||||||
return r == '\n' || r == '\r'
|
return r == '\n' || r == '\r'
|
||||||
}
|
}
|
||||||
@@ -69,82 +63,3 @@ func skipSpaces(r *bufio.Reader) error {
|
|||||||
func unread(r *bufio.Reader) error {
|
func unread(r *bufio.Reader) error {
|
||||||
return r.UnreadRune()
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user