mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-06-14 01:41:57 +08:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fad4bb9c | ||
|
|
189e9bd9da | ||
|
|
98c9b5928a | ||
|
|
e13fd62d38 | ||
|
|
ffacae89eb | ||
|
|
49135fe25e | ||
|
|
2e6402f4b5 | ||
|
|
07f03ebd0c | ||
|
|
92f2676afc | ||
|
|
1807305e6d | ||
|
|
38a97d4531 | ||
|
|
b9f98ecc4a | ||
|
|
1dc222f4b2 | ||
|
|
a79b8de24d | ||
|
|
5da8a93c75 | ||
|
|
b49fc81618 | ||
|
|
6a692453dc | ||
|
|
8d0cceb80c | ||
|
|
e06abf4f6f | ||
|
|
ee555a85da | ||
|
|
1904af2323 | ||
|
|
95b85336d6 | ||
|
|
ca4ce7bce8 | ||
|
|
9065eb90d9 | ||
|
|
50bc361430 | ||
|
|
455a6c8f97 | ||
|
|
04434646eb | ||
|
|
992a56e90b | ||
|
|
ed4d5e5813 | ||
|
|
fe85e7cb42 | ||
|
|
9c6b516bb8 | ||
|
|
2e9063a9a1 | ||
|
|
c3648be533 | ||
|
|
0ab06f62ca | ||
|
|
6170d7b790 | ||
|
|
18d163c4f7 | ||
|
|
a561048d59 | ||
|
|
7a647ca40c | ||
|
|
3f6f14f976 | ||
|
|
a78d57bebd | ||
|
|
74452eb7b5 | ||
|
|
a9e364a01a | ||
|
|
29c2e20b41 | ||
|
|
42c146bcbd | ||
|
|
b61e364458 | ||
|
|
18a4dcb79f | ||
|
|
60a13f1e53 | ||
|
|
3e093bf34e | ||
|
|
211b9498ef | ||
|
|
cca45be3c5 | ||
|
|
e735915d89 | ||
|
|
f77e2c9cfa | ||
|
|
544aa7c432 | ||
|
|
4cef2b412c | ||
|
|
123c61ad12 | ||
|
|
fbf129d535 | ||
|
|
c8a17a97be | ||
|
|
3a493cd6a6 | ||
|
|
7a0c04bc21 | ||
|
|
3c9fe0b381 | ||
|
|
f8b2dc8c9f | ||
|
|
37cb00d789 | ||
|
|
e3e7bc736b | ||
|
|
fafbee24b8 | ||
|
|
8ec29d29ce | ||
|
|
cb7f3e8a17 | ||
|
|
03391b48ca | ||
|
|
d0dedb0624 | ||
|
|
e136deb3a7 | ||
|
|
a2592a17e9 | ||
|
|
05abf4a2ff | ||
|
|
d40000d4b9 | ||
|
|
4620924105 | ||
|
|
a05fe7bf0a | ||
|
|
dd347e96b0 | ||
|
|
a972f400c6 | ||
|
|
fb7664a764 | ||
|
|
7d5d7d9085 | ||
|
|
9911c11e9c | ||
|
|
0d5a68869d | ||
|
|
d9d79e930d |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
|||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
|
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
|
||||||
ethereum: 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan
|
ethereum: # 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan
|
||||||
|
|||||||
13
.github/workflows/go.yml
vendored
13
.github/workflows/go.yml
vendored
@@ -29,8 +29,17 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
go vet -stdmethods=false $(go list ./...)
|
go vet -stdmethods=false $(go list ./...)
|
||||||
go install mvdan.cc/gofumpt@latest
|
|
||||||
test -z "$(gofumpt -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
|
if ! test -z "$(gofmt -l .)"; then
|
||||||
|
echo "Please run 'gofmt -l -w .'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go mod tidy
|
||||||
|
if ! test -z "$(git status --porcelain)"; then
|
||||||
|
echo "Please run 'go mod tidy'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
|
goversion: "https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz"
|
||||||
project_path: "tools/goctl"
|
project_path: "tools/goctl"
|
||||||
binary_name: "goctl"
|
binary_name: "goctl"
|
||||||
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package bloom
|
package bloom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -8,28 +9,29 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
|
||||||
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
|
// maps as k in the error rate table
|
||||||
// maps as k in the error rate table
|
const maps = 14
|
||||||
maps = 14
|
|
||||||
setScript = `
|
var (
|
||||||
|
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
||||||
|
ErrTooLargeOffset = errors.New("too large offset")
|
||||||
|
|
||||||
|
setScript = redis.NewScript(`
|
||||||
for _, offset in ipairs(ARGV) do
|
for _, offset in ipairs(ARGV) do
|
||||||
redis.call("setbit", KEYS[1], offset, 1)
|
redis.call("setbit", KEYS[1], offset, 1)
|
||||||
end
|
end
|
||||||
`
|
`)
|
||||||
testScript = `
|
testScript = redis.NewScript(`
|
||||||
for _, offset in ipairs(ARGV) do
|
for _, offset in ipairs(ARGV) do
|
||||||
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
`
|
`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
|
||||||
var ErrTooLargeOffset = errors.New("too large offset")
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A Filter is a bloom filter.
|
// A Filter is a bloom filter.
|
||||||
Filter struct {
|
Filter struct {
|
||||||
@@ -38,8 +40,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitSetProvider interface {
|
bitSetProvider interface {
|
||||||
check([]uint) (bool, error)
|
check(ctx context.Context, offsets []uint) (bool, error)
|
||||||
set([]uint) error
|
set(ctx context.Context, offsets []uint) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,14 +60,24 @@ func New(store *redis.Redis, key string, bits uint) *Filter {
|
|||||||
|
|
||||||
// Add adds data into f.
|
// Add adds data into f.
|
||||||
func (f *Filter) Add(data []byte) error {
|
func (f *Filter) Add(data []byte) error {
|
||||||
|
return f.AddCtx(context.Background(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCtx adds data into f with context.
|
||||||
|
func (f *Filter) AddCtx(ctx context.Context, data []byte) error {
|
||||||
locations := f.getLocations(data)
|
locations := f.getLocations(data)
|
||||||
return f.bitSet.set(locations)
|
return f.bitSet.set(ctx, locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists checks if data is in f.
|
// Exists checks if data is in f.
|
||||||
func (f *Filter) Exists(data []byte) (bool, error) {
|
func (f *Filter) Exists(data []byte) (bool, error) {
|
||||||
|
return f.ExistsCtx(context.Background(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistsCtx checks if data is in f with context.
|
||||||
|
func (f *Filter) ExistsCtx(ctx context.Context, data []byte) (bool, error) {
|
||||||
locations := f.getLocations(data)
|
locations := f.getLocations(data)
|
||||||
isSet, err := f.bitSet.check(locations)
|
isSet, err := f.bitSet.check(ctx, locations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -111,13 +123,13 @@ func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
|
|||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisBitSet) check(offsets []uint) (bool, error) {
|
func (r *redisBitSet) check(ctx context.Context, offsets []uint) (bool, error) {
|
||||||
args, err := r.buildOffsetArgs(offsets)
|
args, err := r.buildOffsetArgs(offsets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := r.store.Eval(testScript, []string{r.key}, args)
|
resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args)
|
||||||
if err == redis.Nil {
|
if err == redis.Nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -132,22 +144,24 @@ func (r *redisBitSet) check(offsets []uint) (bool, error) {
|
|||||||
return exists == 1, nil
|
return exists == 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// del only use for testing.
|
||||||
func (r *redisBitSet) del() error {
|
func (r *redisBitSet) del() error {
|
||||||
_, err := r.store.Del(r.key)
|
_, err := r.store.Del(r.key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expire only use for testing.
|
||||||
func (r *redisBitSet) expire(seconds int) error {
|
func (r *redisBitSet) expire(seconds int) error {
|
||||||
return r.store.Expire(r.key, seconds)
|
return r.store.Expire(r.key, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisBitSet) set(offsets []uint) error {
|
func (r *redisBitSet) set(ctx context.Context, offsets []uint) error {
|
||||||
args, err := r.buildOffsetArgs(offsets)
|
args, err := r.buildOffsetArgs(offsets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.store.Eval(setScript, []string{r.key}, args)
|
_, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args)
|
||||||
if err == redis.Nil {
|
if err == redis.Nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
package bloom
|
package bloom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
ctx := context.Background()
|
||||||
defer clean()
|
|
||||||
|
|
||||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||||
isSetBefore, err := bitSet.check([]uint{0})
|
isSetBefore, err := bitSet.check(ctx, []uint{0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if isSetBefore {
|
if isSetBefore {
|
||||||
t.Fatal("Bit should not be set")
|
t.Fatal("Bit should not be set")
|
||||||
}
|
}
|
||||||
err = bitSet.set([]uint{512})
|
err = bitSet.set(ctx, []uint{512})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
isSetAfter, err := bitSet.check([]uint{512})
|
isSetAfter, err := bitSet.check(ctx, []uint{512})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -42,9 +43,7 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedisBitSet_Add(t *testing.T) {
|
func TestRedisBitSet_Add(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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")))
|
||||||
@@ -53,3 +52,51 @@ func TestRedisBitSet_Add(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilter_Exists(t *testing.T) {
|
||||||
|
store, clean := redistest.CreateRedisWithClean(t)
|
||||||
|
|
||||||
|
rbs := New(store, "test", 64)
|
||||||
|
_, err := rbs.Exists([]byte{0, 1, 2})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
clean()
|
||||||
|
rbs = New(store, "test", 64)
|
||||||
|
_, err = rbs.Exists([]byte{0, 1, 2})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisBitSet_check(t *testing.T) {
|
||||||
|
store, clean := redistest.CreateRedisWithClean(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rbs := newRedisBitSet(store, "test", 0)
|
||||||
|
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||||
|
_, err := rbs.check(ctx, []uint{0, 1, 2})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
rbs = newRedisBitSet(store, "test", 64)
|
||||||
|
_, err = rbs.check(ctx, []uint{0, 1, 2})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
clean()
|
||||||
|
rbs = newRedisBitSet(store, "test", 64)
|
||||||
|
_, err = rbs.check(ctx, []uint{0, 1, 2})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisBitSet_set(t *testing.T) {
|
||||||
|
logx.Disable()
|
||||||
|
store, clean := redistest.CreateRedisWithClean(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rbs := newRedisBitSet(store, "test", 0)
|
||||||
|
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||||
|
|
||||||
|
rbs = newRedisBitSet(store, "test", 64)
|
||||||
|
assert.NoError(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||||
|
|
||||||
|
clean()
|
||||||
|
rbs = newRedisBitSet(store, "test", 64)
|
||||||
|
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package codec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -21,3 +23,45 @@ func TestGzip(t *testing.T) {
|
|||||||
assert.True(t, len(bs) < buf.Len())
|
assert.True(t, len(bs) < buf.Len())
|
||||||
assert.Equal(t, buf.Bytes(), actual)
|
assert.Equal(t, buf.Bytes(), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGunzip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expected []byte
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid input",
|
||||||
|
input: func() []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
gz := gzip.NewWriter(&buf)
|
||||||
|
gz.Write([]byte("hello"))
|
||||||
|
gz.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}(),
|
||||||
|
expected: []byte("hello"),
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid input",
|
||||||
|
input: []byte("invalid input"),
|
||||||
|
expected: nil,
|
||||||
|
expectedErr: gzip.ErrHeader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := Gunzip(test.input)
|
||||||
|
|
||||||
|
if !bytes.Equal(result, test.expected) {
|
||||||
|
t.Errorf("unexpected result: %v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, test.expectedErr) {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/internal/encoding"
|
"github.com/zeromicro/go-zero/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const jsonTagKey = "json"
|
const (
|
||||||
|
jsonTagKey = "json"
|
||||||
|
jsonTagSep = ','
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
|
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
|
||||||
@@ -76,7 +79,7 @@ func LoadFromJsonBytes(content []byte, v any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var m map[string]any
|
var m map[string]any
|
||||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
if err = jsonx.Unmarshal(content, &m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +130,7 @@ func MustLoad(path string, v any, opts ...Option) {
|
|||||||
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
|
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
|
||||||
if prev, ok := info.children[key]; ok {
|
if prev, ok := info.children[key]; ok {
|
||||||
if child.mapField != nil {
|
if child.mapField != nil {
|
||||||
return newDupKeyError(key)
|
return newConflictKeyError(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mergeFields(prev, key, child.children); err != nil {
|
if err := mergeFields(prev, key, child.children); err != nil {
|
||||||
@@ -160,7 +163,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := info.children[lowerCaseName]; ok {
|
if _, ok := info.children[lowerCaseName]; ok {
|
||||||
return newDupKeyError(lowerCaseName)
|
return newConflictKeyError(lowerCaseName)
|
||||||
}
|
}
|
||||||
|
|
||||||
info.children[lowerCaseName] = &fieldInfo{
|
info.children[lowerCaseName] = &fieldInfo{
|
||||||
@@ -169,7 +172,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if _, ok := info.children[lowerCaseName]; ok {
|
if _, ok := info.children[lowerCaseName]; ok {
|
||||||
return newDupKeyError(lowerCaseName)
|
return newConflictKeyError(lowerCaseName)
|
||||||
}
|
}
|
||||||
|
|
||||||
info.children[lowerCaseName] = &fieldInfo{
|
info.children[lowerCaseName] = &fieldInfo{
|
||||||
@@ -239,7 +242,11 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
|||||||
|
|
||||||
for i := 0; i < tp.NumField(); i++ {
|
for i := 0; i < tp.NumField(); i++ {
|
||||||
field := tp.Field(i)
|
field := tp.Field(i)
|
||||||
name := field.Name
|
if !field.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := getTagName(field)
|
||||||
lowerCaseName := toLowerCase(name)
|
lowerCaseName := toLowerCase(name)
|
||||||
ft := mapping.Deref(field.Type)
|
ft := mapping.Deref(field.Type)
|
||||||
// flatten anonymous fields
|
// flatten anonymous fields
|
||||||
@@ -255,15 +262,32 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getTagName get the tag name of the given field, if no tag name, use file.Name.
|
||||||
|
// field.Name is returned on tags like `json:""` and `json:",optional"`.
|
||||||
|
func getTagName(field reflect.StructField) string {
|
||||||
|
if tag, ok := field.Tag.Lookup(jsonTagKey); ok {
|
||||||
|
if pos := strings.IndexByte(tag, jsonTagSep); pos >= 0 {
|
||||||
|
tag = tag[:pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = strings.TrimSpace(tag)
|
||||||
|
if len(tag) > 0 {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.Name
|
||||||
|
}
|
||||||
|
|
||||||
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo) error {
|
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo) error {
|
||||||
if len(prev.children) == 0 || len(children) == 0 {
|
if len(prev.children) == 0 || len(children) == 0 {
|
||||||
return newDupKeyError(key)
|
return newConflictKeyError(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge fields
|
// merge fields
|
||||||
for k, v := range children {
|
for k, v := range children {
|
||||||
if _, ok := prev.children[k]; ok {
|
if _, ok := prev.children[k]; ok {
|
||||||
return newDupKeyError(k)
|
return newConflictKeyError(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.children[k] = v
|
prev.children[k] = v
|
||||||
@@ -314,14 +338,14 @@ func toLowerCaseKeyMap(m map[string]any, info *fieldInfo) map[string]any {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
type dupKeyError struct {
|
type conflictKeyError struct {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDupKeyError(key string) dupKeyError {
|
func newConflictKeyError(key string) conflictKeyError {
|
||||||
return dupKeyError{key: key}
|
return conflictKeyError{key: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e dupKeyError) Error() string {
|
func (e conflictKeyError) Error() string {
|
||||||
return fmt.Sprintf("duplicated key %s", e.key)
|
return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/hash"
|
"github.com/zeromicro/go-zero/core/hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dupErr dupKeyError
|
var dupErr conflictKeyError
|
||||||
|
|
||||||
func TestLoadConfig_notExists(t *testing.T) {
|
func TestLoadConfig_notExists(t *testing.T) {
|
||||||
assert.NotNil(t, Load("not_a_file", nil))
|
assert.NotNil(t, Load("not_a_file", nil))
|
||||||
@@ -123,6 +123,24 @@ d = "abcd"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigWithLower(t *testing.T) {
|
||||||
|
text := `a = "foo"
|
||||||
|
b = 1
|
||||||
|
`
|
||||||
|
tmpfile, err := createTempFile(".toml", text)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(tmpfile)
|
||||||
|
|
||||||
|
var val struct {
|
||||||
|
A string `json:"a"`
|
||||||
|
b int
|
||||||
|
}
|
||||||
|
if assert.NoError(t, Load(tmpfile, &val)) {
|
||||||
|
assert.Equal(t, "foo", val.A)
|
||||||
|
assert.Equal(t, 0, val.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigJsonCanonical(t *testing.T) {
|
func TestConfigJsonCanonical(t *testing.T) {
|
||||||
text := []byte(`{"a": "foo", "B": "bar"}`)
|
text := []byte(`{"a": "foo", "B": "bar"}`)
|
||||||
|
|
||||||
@@ -672,7 +690,7 @@ func Test_FieldOverwrite(t *testing.T) {
|
|||||||
input := []byte(`{"Name": "hello"}`)
|
input := []byte(`{"Name": "hello"}`)
|
||||||
err := LoadFromJsonBytes(input, val)
|
err := LoadFromJsonBytes(input, val)
|
||||||
assert.ErrorAs(t, err, &dupErr)
|
assert.ErrorAs(t, err, &dupErr)
|
||||||
assert.Equal(t, newDupKeyError("name").Error(), err.Error())
|
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(&St1{})
|
validate(&St1{})
|
||||||
@@ -715,7 +733,7 @@ func Test_FieldOverwrite(t *testing.T) {
|
|||||||
input := []byte(`{"Name": "hello"}`)
|
input := []byte(`{"Name": "hello"}`)
|
||||||
err := LoadFromJsonBytes(input, val)
|
err := LoadFromJsonBytes(input, val)
|
||||||
assert.ErrorAs(t, err, &dupErr)
|
assert.ErrorAs(t, err, &dupErr)
|
||||||
assert.Equal(t, newDupKeyError("name").Error(), err.Error())
|
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(&St0{})
|
validate(&St0{})
|
||||||
@@ -1022,22 +1040,22 @@ func TestLoadNamedFieldOverwritten(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTempFile(ext, text string) (string, error) {
|
func TestLoadLowerMemberShouldNotConflict(t *testing.T) {
|
||||||
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
type (
|
||||||
if err != nil {
|
Redis struct {
|
||||||
return "", err
|
db uint
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
Config struct {
|
||||||
return "", err
|
db uint
|
||||||
}
|
Redis
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
filename := tmpfile.Name()
|
var c Config
|
||||||
if err = tmpfile.Close(); err != nil {
|
assert.NoError(t, LoadFromJsonBytes([]byte(`{}`), &c))
|
||||||
return "", err
|
assert.Zero(t, c.db)
|
||||||
}
|
assert.Zero(t, c.Redis.db)
|
||||||
|
|
||||||
return filename, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFillDefaultUnmarshal(t *testing.T) {
|
func TestFillDefaultUnmarshal(t *testing.T) {
|
||||||
@@ -1079,7 +1097,7 @@ func TestFillDefaultUnmarshal(t *testing.T) {
|
|||||||
assert.Equal(t, st.C, "c")
|
assert.Equal(t, st.C, "c")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("has vaue", func(t *testing.T) {
|
t.Run("has value", func(t *testing.T) {
|
||||||
type St struct {
|
type St struct {
|
||||||
A string `json:",default=a"`
|
A string `json:",default=a"`
|
||||||
B string
|
B string
|
||||||
@@ -1091,3 +1109,107 @@ func TestFillDefaultUnmarshal(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigWithJsonTag(t *testing.T) {
|
||||||
|
t.Run("map with value", func(t *testing.T) {
|
||||||
|
var input = []byte(`[Value]
|
||||||
|
[Value.first]
|
||||||
|
Email = "foo"
|
||||||
|
[Value.second]
|
||||||
|
Email = "bar"`)
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ValueMap map[string]Value `json:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
||||||
|
assert.Len(t, c.ValueMap, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map with ptr value", func(t *testing.T) {
|
||||||
|
var input = []byte(`[Value]
|
||||||
|
[Value.first]
|
||||||
|
Email = "foo"
|
||||||
|
[Value.second]
|
||||||
|
Email = "bar"`)
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ValueMap map[string]*Value `json:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
||||||
|
assert.Len(t, c.ValueMap, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map with optional", func(t *testing.T) {
|
||||||
|
var input = []byte(`[Value]
|
||||||
|
[Value.first]
|
||||||
|
Email = "foo"
|
||||||
|
[Value.second]
|
||||||
|
Email = "bar"`)
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Value map[string]Value `json:",optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
||||||
|
assert.Len(t, c.Value, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("map with empty tag", func(t *testing.T) {
|
||||||
|
var input = []byte(`[Value]
|
||||||
|
[Value.first]
|
||||||
|
Email = "foo"
|
||||||
|
[Value.second]
|
||||||
|
Email = "bar"`)
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Value map[string]Value `json:" "`
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
if assert.NoError(t, LoadFromTomlBytes(input, &c)) {
|
||||||
|
assert.Len(t, c.Value, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempFile(ext, text string) (string, error) {
|
||||||
|
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := tmpFile.Name()
|
||||||
|
if err = tmpFile.Close(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var (
|
|||||||
type EtcdConf struct {
|
type EtcdConf struct {
|
||||||
Hosts []string
|
Hosts []string
|
||||||
Key string
|
Key string
|
||||||
|
ID int64 `json:",optional"`
|
||||||
User string `json:",optional"`
|
User string `json:",optional"`
|
||||||
Pass string `json:",optional"`
|
Pass string `json:",optional"`
|
||||||
CertFile string `json:",optional"`
|
CertFile string `json:",optional"`
|
||||||
@@ -26,6 +27,11 @@ func (c EtcdConf) HasAccount() bool {
|
|||||||
return len(c.User) > 0 && len(c.Pass) > 0
|
return len(c.User) > 0 && len(c.Pass) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasID returns if ID provided.
|
||||||
|
func (c EtcdConf) HasID() bool {
|
||||||
|
return c.ID > 0
|
||||||
|
}
|
||||||
|
|
||||||
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
|
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
|
||||||
func (c EtcdConf) HasTLS() bool {
|
func (c EtcdConf) HasTLS() bool {
|
||||||
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0
|
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0
|
||||||
|
|||||||
@@ -80,3 +80,90 @@ func TestEtcdConf_HasAccount(t *testing.T) {
|
|||||||
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
|
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEtcdConf_HasID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
EtcdConf
|
||||||
|
hasServerID bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
EtcdConf: EtcdConf{
|
||||||
|
Hosts: []string{"any"},
|
||||||
|
ID: -1,
|
||||||
|
},
|
||||||
|
hasServerID: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EtcdConf: EtcdConf{
|
||||||
|
Hosts: []string{"any"},
|
||||||
|
ID: 0,
|
||||||
|
},
|
||||||
|
hasServerID: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EtcdConf: EtcdConf{
|
||||||
|
Hosts: []string{"any"},
|
||||||
|
ID: 10000,
|
||||||
|
},
|
||||||
|
hasServerID: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
assert.Equal(t, test.hasServerID, test.EtcdConf.HasID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdConf_HasTLS(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
conf EtcdConf
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
conf: EtcdConf{},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing CertFile",
|
||||||
|
conf: EtcdConf{
|
||||||
|
CertKeyFile: "key",
|
||||||
|
CACertFile: "ca",
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing CertKeyFile",
|
||||||
|
conf: EtcdConf{
|
||||||
|
CertFile: "cert",
|
||||||
|
CACertFile: "ca",
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing CACertFile",
|
||||||
|
conf: EtcdConf{
|
||||||
|
CertFile: "cert",
|
||||||
|
CertKeyFile: "key",
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
conf: EtcdConf{
|
||||||
|
CertFile: "cert",
|
||||||
|
CertKeyFile: "key",
|
||||||
|
CACertFile: "ca",
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := tt.conf.HasTLS()
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,85 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
certContent = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
|
||||||
|
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||||
|
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
|
||||||
|
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
|
||||||
|
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
|
||||||
|
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
|
||||||
|
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
|
||||||
|
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
|
||||||
|
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
|
||||||
|
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
|
||||||
|
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
|
||||||
|
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
|
||||||
|
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
|
||||||
|
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
|
||||||
|
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
|
||||||
|
xkWYxRPegajuEZGvCqVs
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
keyContent = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
|
||||||
|
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
|
||||||
|
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
|
||||||
|
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
|
||||||
|
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
|
||||||
|
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
|
||||||
|
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
|
||||||
|
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
|
||||||
|
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
|
||||||
|
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
|
||||||
|
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
|
||||||
|
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
|
||||||
|
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
|
||||||
|
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
|
||||||
|
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
|
||||||
|
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
|
||||||
|
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
|
||||||
|
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
|
||||||
|
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
|
||||||
|
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
|
||||||
|
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
|
||||||
|
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
|
||||||
|
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
|
||||||
|
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
|
||||||
|
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
caContent = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
|
||||||
|
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||||
|
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
|
||||||
|
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
|
||||||
|
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
|
||||||
|
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
|
||||||
|
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
|
||||||
|
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
|
||||||
|
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
|
||||||
|
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
|
||||||
|
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
|
||||||
|
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
|
||||||
|
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
|
||||||
|
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
|
||||||
|
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
|
||||||
|
E2oTTM0rYKOZ8p6000mhvKI=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
)
|
||||||
|
|
||||||
func TestAccount(t *testing.T) {
|
func TestAccount(t *testing.T) {
|
||||||
endpoints := []string{
|
endpoints := []string{
|
||||||
"192.168.0.2:2379",
|
"192.168.0.2:2379",
|
||||||
@@ -32,3 +105,34 @@ func TestAccount(t *testing.T) {
|
|||||||
assert.Equal(t, username, account.User)
|
assert.Equal(t, username, account.User)
|
||||||
assert.Equal(t, anotherPassword, account.Pass)
|
assert.Equal(t, anotherPassword, account.Pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTLSMethods(t *testing.T) {
|
||||||
|
certFile := createTempFile(t, []byte(certContent))
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
keyFile := createTempFile(t, []byte(keyContent))
|
||||||
|
defer os.Remove(keyFile)
|
||||||
|
caFile := createTempFile(t, []byte(caContent))
|
||||||
|
defer os.Remove(caFile)
|
||||||
|
|
||||||
|
assert.NoError(t, AddTLS([]string{"foo"}, certFile, keyFile, caFile, false))
|
||||||
|
cfg, ok := GetTLS([]string{"foo"})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
|
||||||
|
assert.Error(t, AddTLS([]string{"bar"}, "bad-file", keyFile, caFile, false))
|
||||||
|
assert.Error(t, AddTLS([]string{"bar"}, certFile, keyFile, "bad-file", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempFile(t *testing.T, body []byte) string {
|
||||||
|
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile.Close()
|
||||||
|
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpFile.Name()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
||||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
"go.etcd.io/etcd/client/v3/mock/mockserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockLock sync.Mutex
|
var mockLock sync.Mutex
|
||||||
@@ -242,3 +245,58 @@ func TestValueOnlyContext(t *testing.T) {
|
|||||||
ctx.Done()
|
ctx.Done()
|
||||||
assert.Nil(t, ctx.Err())
|
assert.Nil(t, ctx.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDialClient(t *testing.T) {
|
||||||
|
svr, err := mockserver.StartMockServers(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
svr.StartAt(0)
|
||||||
|
|
||||||
|
certFile := createTempFile(t, []byte(certContent))
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
keyFile := createTempFile(t, []byte(keyContent))
|
||||||
|
defer os.Remove(keyFile)
|
||||||
|
caFile := createTempFile(t, []byte(caContent))
|
||||||
|
defer os.Remove(caFile)
|
||||||
|
|
||||||
|
endpoints := []string{svr.Servers[0].Address}
|
||||||
|
AddAccount(endpoints, "foo", "bar")
|
||||||
|
assert.NoError(t, AddTLS(endpoints, certFile, keyFile, caFile, false))
|
||||||
|
|
||||||
|
old := DialTimeout
|
||||||
|
DialTimeout = time.Millisecond
|
||||||
|
defer func() {
|
||||||
|
DialTimeout = old
|
||||||
|
}()
|
||||||
|
_, err = DialClient(endpoints)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Monitor(t *testing.T) {
|
||||||
|
svr, err := mockserver.StartMockServers(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
svr.StartAt(0)
|
||||||
|
|
||||||
|
endpoints := []string{svr.Servers[0].Address}
|
||||||
|
GetRegistry().lock.Lock()
|
||||||
|
GetRegistry().clusters = map[string]*cluster{
|
||||||
|
getClusterKey(endpoints): {
|
||||||
|
listeners: map[string][]UpdateListener{},
|
||||||
|
values: map[string]map[string]string{
|
||||||
|
"foo": {
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
GetRegistry().lock.Unlock()
|
||||||
|
assert.Error(t, GetRegistry().Monitor(endpoints, "foo", new(mockListener)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockListener struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockListener) OnAdd(_ KV) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockListener) OnDelete(_ KV) {
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package discov
|
package discov
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +16,83 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/resolver/manual"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
certContent = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
|
||||||
|
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||||
|
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
|
||||||
|
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
|
||||||
|
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
|
||||||
|
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
|
||||||
|
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
|
||||||
|
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
|
||||||
|
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
|
||||||
|
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
|
||||||
|
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
|
||||||
|
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
|
||||||
|
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
|
||||||
|
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
|
||||||
|
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
|
||||||
|
xkWYxRPegajuEZGvCqVs
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
keyContent = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
|
||||||
|
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
|
||||||
|
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
|
||||||
|
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
|
||||||
|
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
|
||||||
|
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
|
||||||
|
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
|
||||||
|
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
|
||||||
|
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
|
||||||
|
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
|
||||||
|
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
|
||||||
|
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
|
||||||
|
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
|
||||||
|
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
|
||||||
|
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
|
||||||
|
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
|
||||||
|
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
|
||||||
|
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
|
||||||
|
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
|
||||||
|
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
|
||||||
|
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
|
||||||
|
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
|
||||||
|
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
|
||||||
|
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
|
||||||
|
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
caContent = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
|
||||||
|
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||||
|
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
|
||||||
|
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
|
||||||
|
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
|
||||||
|
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
|
||||||
|
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
|
||||||
|
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
|
||||||
|
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
|
||||||
|
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
|
||||||
|
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
|
||||||
|
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
|
||||||
|
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
|
||||||
|
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
|
||||||
|
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
|
||||||
|
E2oTTM0rYKOZ8p6000mhvKI=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -37,7 +117,7 @@ func TestPublisher_register(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublisher_registerWithId(t *testing.T) {
|
func TestPublisher_registerWithOptions(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
const id = 2
|
const id = 2
|
||||||
@@ -49,7 +129,15 @@ func TestPublisher_registerWithId(t *testing.T) {
|
|||||||
ID: 1,
|
ID: 1,
|
||||||
}, nil)
|
}, nil)
|
||||||
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
|
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
|
||||||
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id))
|
|
||||||
|
certFile := createTempFile(t, []byte(certContent))
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
keyFile := createTempFile(t, []byte(keyContent))
|
||||||
|
defer os.Remove(keyFile)
|
||||||
|
caFile := createTempFile(t, []byte(caContent))
|
||||||
|
defer os.Remove(caFile)
|
||||||
|
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id),
|
||||||
|
WithPubEtcdTLS(certFile, keyFile, caFile, true))
|
||||||
_, err := pub.register(cli)
|
_, err := pub.register(cli)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
@@ -169,3 +257,92 @@ func TestPublisher_Resume(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
<-publisher.resumeChan
|
<-publisher.resumeChan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublisher_keepAliveAsync(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
const id clientv3.LeaseID = 1
|
||||||
|
conn := createMockConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
cli := internal.NewMockEtcdClient(ctrl)
|
||||||
|
cli.EXPECT().ActiveConnection().Return(conn).AnyTimes()
|
||||||
|
cli.EXPECT().Close()
|
||||||
|
defer cli.Close()
|
||||||
|
cli.ActiveConnection()
|
||||||
|
restore := setMockClient(cli)
|
||||||
|
defer restore()
|
||||||
|
cli.EXPECT().Ctx().AnyTimes()
|
||||||
|
cli.EXPECT().KeepAlive(gomock.Any(), id)
|
||||||
|
cli.EXPECT().Grant(gomock.Any(), timeToLive).Return(&clientv3.LeaseGrantResponse{
|
||||||
|
ID: 1,
|
||||||
|
}, nil)
|
||||||
|
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", int64(id)), "thevalue", gomock.Any())
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
pub := NewPublisher([]string{"the-endpoint"}, "thekey", "thevalue")
|
||||||
|
pub.lease = id
|
||||||
|
assert.Nil(t, pub.KeepAlive())
|
||||||
|
pub.Stop()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMockConn(t *testing.T) *grpc.ClientConn {
|
||||||
|
lis, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error while listening. Err: %v", err)
|
||||||
|
}
|
||||||
|
defer lis.Close()
|
||||||
|
lisAddr := resolver.Address{Addr: lis.Addr().String()}
|
||||||
|
lisDone := make(chan struct{})
|
||||||
|
dialDone := make(chan struct{})
|
||||||
|
// 1st listener accepts the connection and then does nothing
|
||||||
|
go func() {
|
||||||
|
defer close(lisDone)
|
||||||
|
conn, err := lis.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error while accepting. Err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
framer := http2.NewFramer(conn, conn)
|
||||||
|
if err := framer.WriteSettings(http2.Setting{}); err != nil {
|
||||||
|
t.Errorf("Error while writing settings. Err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
<-dialDone // Close conn only after dial returns.
|
||||||
|
}()
|
||||||
|
|
||||||
|
r := manual.NewBuilderWithScheme("whatever")
|
||||||
|
r.InitialState(resolver.State{Addresses: []resolver.Address{lisAddr}})
|
||||||
|
client, err := grpc.DialContext(context.Background(), r.Scheme()+":///test.server",
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r))
|
||||||
|
close(dialDone)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial failed. Err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.After(1 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
t.Fatal("timed out waiting for server to finish")
|
||||||
|
case <-lisDone:
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempFile(t *testing.T, body []byte) string {
|
||||||
|
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile.Close()
|
||||||
|
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpFile.Name()
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (pe *PeriodicalExecutor) Flush() bool {
|
|||||||
}())
|
}())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync lets caller to run fn thread-safe with pe, especially for the underlying container.
|
// Sync lets caller run fn thread-safe with pe, especially for the underlying container.
|
||||||
func (pe *PeriodicalExecutor) Sync(fn func()) {
|
func (pe *PeriodicalExecutor) Sync(fn func()) {
|
||||||
pe.lock.Lock()
|
pe.lock.Lock()
|
||||||
defer pe.lock.Unlock()
|
defer pe.lock.Unlock()
|
||||||
@@ -116,7 +116,7 @@ func (pe *PeriodicalExecutor) addAndCheck(task any) (any, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) backgroundFlush() {
|
func (pe *PeriodicalExecutor) backgroundFlush() {
|
||||||
threading.GoSafe(func() {
|
go func() {
|
||||||
// flush before quit goroutine to avoid missing tasks
|
// flush before quit goroutine to avoid missing tasks
|
||||||
defer pe.Flush()
|
defer pe.Flush()
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) doneExecution() {
|
func (pe *PeriodicalExecutor) doneExecution() {
|
||||||
@@ -162,7 +162,9 @@ func (pe *PeriodicalExecutor) executeTasks(tasks any) bool {
|
|||||||
|
|
||||||
ok := pe.hasTasks(tasks)
|
ok := pe.hasTasks(tasks)
|
||||||
if ok {
|
if ok {
|
||||||
pe.container.Execute(tasks)
|
threading.RunSafe(func() {
|
||||||
|
pe.container.Execute(tasks)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
|
|||||||
@@ -108,6 +108,64 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
|||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPeriodicalExecutor_Panic(t *testing.T) {
|
||||||
|
// avoid data race
|
||||||
|
var lock sync.Mutex
|
||||||
|
ticker := timex.NewFakeTicker()
|
||||||
|
|
||||||
|
var (
|
||||||
|
executedTasks []int
|
||||||
|
expected []int
|
||||||
|
)
|
||||||
|
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
|
||||||
|
tt := tasks.([]int)
|
||||||
|
lock.Lock()
|
||||||
|
executedTasks = append(executedTasks, tt...)
|
||||||
|
lock.Unlock()
|
||||||
|
if tt[0] == 0 {
|
||||||
|
panic("test")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
executor.newTicker = func(duration time.Duration) timex.Ticker {
|
||||||
|
return ticker
|
||||||
|
}
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
executor.Add(i)
|
||||||
|
expected = append(expected, i)
|
||||||
|
}
|
||||||
|
ticker.Tick()
|
||||||
|
ticker.Tick()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
lock.Lock()
|
||||||
|
assert.Equal(t, expected, executedTasks)
|
||||||
|
lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeriodicalExecutor_FlushPanic(t *testing.T) {
|
||||||
|
var (
|
||||||
|
executedTasks []int
|
||||||
|
expected []int
|
||||||
|
lock sync.Mutex
|
||||||
|
)
|
||||||
|
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
|
||||||
|
tt := tasks.([]int)
|
||||||
|
lock.Lock()
|
||||||
|
executedTasks = append(executedTasks, tt...)
|
||||||
|
lock.Unlock()
|
||||||
|
if tt[0] == 0 {
|
||||||
|
panic("flush panic")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
executor.Add(i)
|
||||||
|
expected = append(expected, i)
|
||||||
|
}
|
||||||
|
executor.Flush()
|
||||||
|
lock.Lock()
|
||||||
|
assert.Equal(t, expected, executedTasks)
|
||||||
|
lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_Wait(t *testing.T) {
|
func TestPeriodicalExecutor_Wait(t *testing.T) {
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
executer := NewBulkExecutor(func(tasks []any) {
|
executer := NewBulkExecutor(func(tasks []any) {
|
||||||
@@ -151,13 +209,7 @@ func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
|
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
|
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
|
||||||
exec.newTicker = func(d time.Duration) timex.Ticker {
|
|
||||||
return ticker
|
|
||||||
}
|
|
||||||
assert.False(t, exec.hasTasks(nil))
|
assert.False(t, exec.hasTasks(nil))
|
||||||
assert.True(t, exec.hasTasks(1))
|
assert.True(t, exec.hasTasks(1))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
|
|||||||
@@ -9,21 +9,6 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
|
||||||
const periodScript = `local limit = tonumber(ARGV[1])
|
|
||||||
local window = tonumber(ARGV[2])
|
|
||||||
local current = redis.call("INCRBY", KEYS[1], 1)
|
|
||||||
if current == 1 then
|
|
||||||
redis.call("expire", KEYS[1], window)
|
|
||||||
end
|
|
||||||
if current < limit then
|
|
||||||
return 1
|
|
||||||
elseif current == limit then
|
|
||||||
return 2
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
end`
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Unknown means not initialized state.
|
// Unknown means not initialized state.
|
||||||
Unknown = iota
|
Unknown = iota
|
||||||
@@ -39,8 +24,25 @@ const (
|
|||||||
internalHitQuota = 2
|
internalHitQuota = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownCode is an error that represents unknown status code.
|
var (
|
||||||
var ErrUnknownCode = errors.New("unknown status code")
|
// ErrUnknownCode is an error that represents unknown status code.
|
||||||
|
ErrUnknownCode = errors.New("unknown status code")
|
||||||
|
|
||||||
|
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||||
|
periodScript = redis.NewScript(`local limit = tonumber(ARGV[1])
|
||||||
|
local window = tonumber(ARGV[2])
|
||||||
|
local current = redis.call("INCRBY", KEYS[1], 1)
|
||||||
|
if current == 1 then
|
||||||
|
redis.call("expire", KEYS[1], window)
|
||||||
|
end
|
||||||
|
if current < limit then
|
||||||
|
return 1
|
||||||
|
elseif current == limit then
|
||||||
|
return 2
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end`)
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// PeriodOption defines the method to customize a PeriodLimit.
|
// PeriodOption defines the method to customize a PeriodLimit.
|
||||||
@@ -80,7 +82,7 @@ func (h *PeriodLimit) Take(key string) (int, error) {
|
|||||||
|
|
||||||
// TakeCtx requests a permit with context, it returns the permit state.
|
// TakeCtx requests a permit with context, it returns the permit state.
|
||||||
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
||||||
resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
resp, err := h.limitStore.ScriptRunCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
||||||
strconv.Itoa(h.quota),
|
strconv.Itoa(h.quota),
|
||||||
strconv.Itoa(h.calcExpireSeconds()),
|
strconv.Itoa(h.calcExpireSeconds()),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
|
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
seconds = 1
|
seconds = 1
|
||||||
|
|||||||
@@ -15,10 +15,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
tokenFormat = "{%s}.tokens"
|
||||||
// KEYS[1] as tokens_key
|
timestampFormat = "{%s}.ts"
|
||||||
// KEYS[2] as timestamp_key
|
pingInterval = time.Millisecond * 100
|
||||||
script = `local rate = tonumber(ARGV[1])
|
)
|
||||||
|
|
||||||
|
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||||
|
// KEYS[1] as tokens_key
|
||||||
|
// KEYS[2] as timestamp_key
|
||||||
|
var script = redis.NewScript(`local rate = tonumber(ARGV[1])
|
||||||
local capacity = tonumber(ARGV[2])
|
local capacity = tonumber(ARGV[2])
|
||||||
local now = tonumber(ARGV[3])
|
local now = tonumber(ARGV[3])
|
||||||
local requested = tonumber(ARGV[4])
|
local requested = tonumber(ARGV[4])
|
||||||
@@ -45,11 +50,7 @@ end
|
|||||||
redis.call("setex", KEYS[1], ttl, new_tokens)
|
redis.call("setex", KEYS[1], ttl, new_tokens)
|
||||||
redis.call("setex", KEYS[2], ttl, now)
|
redis.call("setex", KEYS[2], ttl, now)
|
||||||
|
|
||||||
return allowed`
|
return allowed`)
|
||||||
tokenFormat = "{%s}.tokens"
|
|
||||||
timestampFormat = "{%s}.ts"
|
|
||||||
pingInterval = time.Millisecond * 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
||||||
type TokenLimiter struct {
|
type TokenLimiter struct {
|
||||||
@@ -110,7 +111,7 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
|
|||||||
return lim.rescueLimiter.AllowN(now, n)
|
return lim.rescueLimiter.AllowN(now, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := lim.store.EvalCtx(ctx,
|
resp, err := lim.store.ScriptRunCtx(ctx,
|
||||||
script,
|
script,
|
||||||
[]string{
|
[]string{
|
||||||
lim.tokenKey,
|
lim.tokenKey,
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_Take(t *testing.T) {
|
func TestTokenLimit_Take(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
@@ -92,9 +90,7 @@ func TestTokenLimit_Take(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_TakeBurst(t *testing.T) {
|
func TestTokenLimit_TakeBurst(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type LogConf struct {
|
|||||||
MaxContentLength uint32 `json:",optional"`
|
MaxContentLength uint32 `json:",optional"`
|
||||||
// Compress represents whether to compress the log file, default is `false`.
|
// Compress represents whether to compress the log file, default is `false`.
|
||||||
Compress bool `json:",optional"`
|
Compress bool `json:",optional"`
|
||||||
// Stdout represents whether to log statistics, default is `true`.
|
// Stat represents whether to log statistics, default is `true`.
|
||||||
Stat bool `json:",default=true"`
|
Stat bool `json:",default=true"`
|
||||||
// KeepDays represents how many days the log files will be kept. Default to keep all files.
|
// KeepDays represents how many days the log files will be kept. Default to keep all files.
|
||||||
// Only take effect when Mode is `file` or `volume`, both work when Rotation is `daily` or `size`.
|
// Only take effect when Mode is `file` or `volume`, both work when Rotation is `daily` or `size`.
|
||||||
@@ -38,7 +38,7 @@ type LogConf struct {
|
|||||||
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
|
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
|
||||||
// Only take effect when RotationRuleType is `size`
|
// Only take effect when RotationRuleType is `size`
|
||||||
MaxSize int `json:",default=0"`
|
MaxSize int `json:",default=0"`
|
||||||
// RotationRuleType represents the type of log rotation rule. Default is `daily`.
|
// Rotation represents the type of log rotation rule. Default is `daily`.
|
||||||
// daily: daily rotation.
|
// daily: daily rotation.
|
||||||
// size: size limited rotation.
|
// size: size limited rotation.
|
||||||
Rotation string `json:",default=daily,options=[daily,size]"`
|
Rotation string `json:",default=daily,options=[daily,size]"`
|
||||||
|
|||||||
@@ -197,7 +197,12 @@ func Must(err error) {
|
|||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
getWriter().Severe(msg)
|
getWriter().Severe(msg)
|
||||||
os.Exit(1)
|
|
||||||
|
if ExitOnFatal.True() {
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustSetup sets up logging with given config c. It exits on error.
|
// MustSetup sets up logging with given config c. It exits on error.
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ var (
|
|||||||
_ Writer = (*mockWriter)(nil)
|
_ Writer = (*mockWriter)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ExitOnFatal.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
type mockWriter struct {
|
type mockWriter struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
builder strings.Builder
|
builder strings.Builder
|
||||||
@@ -208,6 +212,12 @@ func TestFileLineConsoleMode(t *testing.T) {
|
|||||||
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMust(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
Must(errors.New("foo"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogAlert(t *testing.T) {
|
func TestStructedLogAlert(t *testing.T) {
|
||||||
w := new(mockWriter)
|
w := new(mockWriter)
|
||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
@@ -574,26 +584,38 @@ func TestSetup(t *testing.T) {
|
|||||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
setupOnce = sync.Once{}
|
||||||
|
MustSetup(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Mode: "console",
|
||||||
|
Encoding: "json",
|
||||||
|
TimeFormat: timeFormat,
|
||||||
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
Mode: "console",
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "file",
|
Mode: "file",
|
||||||
Path: os.TempDir(),
|
Path: os.TempDir(),
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "volume",
|
Mode: "volume",
|
||||||
Path: os.TempDir(),
|
Path: os.TempDir(),
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
Mode: "console",
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
Mode: "console",
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger,
|
|||||||
rule: rule,
|
rule: rule,
|
||||||
compress: compress,
|
compress: compress,
|
||||||
}
|
}
|
||||||
if err := l.init(); err != nil {
|
if err := l.initialize(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ func (l *RotateLogger) getBackupFilename() string {
|
|||||||
return l.backup
|
return l.backup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RotateLogger) init() error {
|
func (l *RotateLogger) initialize() error {
|
||||||
l.backup = l.rule.BackupFileName()
|
l.backup = l.rule.BackupFileName()
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(l.filename); err != nil {
|
if fileInfo, err := os.Stat(l.filename); err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package logx
|
package logx
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DebugLevel logs everything
|
// DebugLevel logs everything
|
||||||
@@ -61,6 +65,8 @@ var (
|
|||||||
ErrLogPathNotSet = errors.New("log path must be set")
|
ErrLogPathNotSet = errors.New("log path must be set")
|
||||||
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
||||||
ErrLogServiceNameNotSet = errors.New("log service name must be set")
|
ErrLogServiceNameNotSet = errors.New("log service name must be set")
|
||||||
|
// ExitOnFatal defines whether to exit on fatal errors, defined here to make it easier to test.
|
||||||
|
ExitOnFatal = syncx.ForAtomicBool(true)
|
||||||
|
|
||||||
truncatedField = Field(truncatedKey, true)
|
truncatedField = Field(truncatedKey, true)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -289,6 +289,10 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
|
|||||||
return reflect.ValueOf(mapValue), nil
|
return reflect.ValueOf(mapValue), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if keyType != valueType.Key() {
|
||||||
|
return emptyValue, errTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
refValue := reflect.ValueOf(mapValue)
|
refValue := reflect.ValueOf(mapValue)
|
||||||
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
||||||
dereffedElemType := Deref(elemType)
|
dereffedElemType := Deref(elemType)
|
||||||
@@ -691,6 +695,10 @@ func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value ref
|
|||||||
|
|
||||||
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
||||||
m valuerWithParent, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
|
if !field.IsExported() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -810,6 +818,11 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if u.opts.fillDefault {
|
if u.opts.fillDefault {
|
||||||
|
if fieldType.Kind() != reflect.Ptr && fieldKind == reflect.Struct {
|
||||||
|
return u.processFieldNotFromString(fieldType, value, valueWithParent{
|
||||||
|
value: emptyMap,
|
||||||
|
}, opts, fullName)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,12 +878,9 @@ func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName
|
|||||||
|
|
||||||
numFields := baseType.NumField()
|
numFields := baseType.NumField()
|
||||||
for i := 0; i < numFields; i++ {
|
for i := 0; i < numFields; i++ {
|
||||||
field := baseType.Field(i)
|
typeField := baseType.Field(i)
|
||||||
if !field.IsExported() {
|
valueField := valElem.Field(i)
|
||||||
continue
|
if err := u.processField(typeField, valueField, m, fullName); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
if err := u.processField(field, valElem.Field(i), m, fullName); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,52 @@ func TestUnmarshalWithoutTagName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithLowerField(t *testing.T) {
|
||||||
|
type (
|
||||||
|
Lower struct {
|
||||||
|
value int `key:"lower"`
|
||||||
|
}
|
||||||
|
|
||||||
|
inner struct {
|
||||||
|
Lower
|
||||||
|
Optional bool `key:",optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
m := map[string]any{
|
||||||
|
"Optional": true,
|
||||||
|
"lower": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
if assert.NoError(t, UnmarshalKey(m, &in)) {
|
||||||
|
assert.True(t, in.Optional)
|
||||||
|
assert.Equal(t, 0, in.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithLowerAnonymousStruct(t *testing.T) {
|
||||||
|
type (
|
||||||
|
lower struct {
|
||||||
|
Value int `key:"lower"`
|
||||||
|
}
|
||||||
|
|
||||||
|
inner struct {
|
||||||
|
lower
|
||||||
|
Optional bool `key:",optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
m := map[string]any{
|
||||||
|
"Optional": true,
|
||||||
|
"lower": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
if assert.NoError(t, UnmarshalKey(m, &in)) {
|
||||||
|
assert.True(t, in.Optional)
|
||||||
|
assert.Equal(t, 1, in.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalWithoutTagNameWithCanonicalKey(t *testing.T) {
|
func TestUnmarshalWithoutTagNameWithCanonicalKey(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Name string `key:"name"`
|
Name string `key:"name"`
|
||||||
@@ -4285,6 +4331,97 @@ func TestUnmarshalOnlyPublicVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFillDefaultUnmarshal(t *testing.T) {
|
||||||
|
fillDefaultUnmarshal := NewUnmarshaler(jsonTagKey, WithDefault())
|
||||||
|
t.Run("nil", func(t *testing.T) {
|
||||||
|
type St struct{}
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, St{})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not nil", func(t *testing.T) {
|
||||||
|
type St struct{}
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &St{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
type St struct {
|
||||||
|
A string `json:",default=a"`
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
var st St
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "a", st.A)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("env", func(t *testing.T) {
|
||||||
|
type St struct {
|
||||||
|
A string `json:",default=a"`
|
||||||
|
B string
|
||||||
|
C string `json:",env=TEST_C"`
|
||||||
|
}
|
||||||
|
t.Setenv("TEST_C", "c")
|
||||||
|
|
||||||
|
var st St
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "a", st.A)
|
||||||
|
assert.Equal(t, "c", st.C)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("has value", func(t *testing.T) {
|
||||||
|
type St struct {
|
||||||
|
A string `json:",default=a"`
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
var st = St{
|
||||||
|
A: "b",
|
||||||
|
}
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("handling struct", func(t *testing.T) {
|
||||||
|
type St struct {
|
||||||
|
A string `json:",default=a"`
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
type St2 struct {
|
||||||
|
St
|
||||||
|
St1 St
|
||||||
|
St3 *St
|
||||||
|
C string `json:",default=c"`
|
||||||
|
D string
|
||||||
|
Child *St2
|
||||||
|
}
|
||||||
|
var st2 St2
|
||||||
|
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "a", st2.St.A)
|
||||||
|
assert.Equal(t, "a", st2.St1.A)
|
||||||
|
assert.Nil(t, st2.St3)
|
||||||
|
assert.Equal(t, "c", st2.C)
|
||||||
|
assert.Nil(t, st2.Child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UnmarshalMap(t *testing.T) {
|
||||||
|
type Customer struct {
|
||||||
|
Names map[int]string `key:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
input := map[string]any{
|
||||||
|
"names": map[string]any{
|
||||||
|
"19": "Tom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var customer Customer
|
||||||
|
assert.ErrorIs(t, UnmarshalKey(input, &customer), errTypeMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkDefaultValue(b *testing.B) {
|
func BenchmarkDefaultValue(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var a struct {
|
var a struct {
|
||||||
@@ -4384,56 +4521,3 @@ func BenchmarkUnmarshal(b *testing.B) {
|
|||||||
UnmarshalKey(data, &an)
|
UnmarshalKey(data, &an)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFillDefaultUnmarshal(t *testing.T) {
|
|
||||||
fillDefaultUnmarshal := NewUnmarshaler(jsonTagKey, WithDefault())
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
type St struct{}
|
|
||||||
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, St{})
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("not nil", func(t *testing.T) {
|
|
||||||
type St struct{}
|
|
||||||
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &St{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default", func(t *testing.T) {
|
|
||||||
type St struct {
|
|
||||||
A string `json:",default=a"`
|
|
||||||
B string
|
|
||||||
}
|
|
||||||
var st St
|
|
||||||
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, st.A, "a")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("env", func(t *testing.T) {
|
|
||||||
type St struct {
|
|
||||||
A string `json:",default=a"`
|
|
||||||
B string
|
|
||||||
C string `json:",env=TEST_C"`
|
|
||||||
}
|
|
||||||
t.Setenv("TEST_C", "c")
|
|
||||||
|
|
||||||
var st St
|
|
||||||
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, st.A, "a")
|
|
||||||
assert.Equal(t, st.C, "c")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("has value", func(t *testing.T) {
|
|
||||||
type St struct {
|
|
||||||
A string `json:",default=a"`
|
|
||||||
B string
|
|
||||||
}
|
|
||||||
var st = St{
|
|
||||||
A: "b",
|
|
||||||
}
|
|
||||||
err := fillDefaultUnmarshal.Unmarshal(map[string]any{}, &st)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ func parseGroupedSegments(val string) []string {
|
|||||||
|
|
||||||
// don't modify returned fieldOptions, it's cached and shared among different calls.
|
// don't modify returned fieldOptions, it's cached and shared among different calls.
|
||||||
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
|
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
|
||||||
value := field.Tag.Get(tagName)
|
value := strings.TrimSpace(field.Tag.Get(tagName))
|
||||||
if len(value) == 0 {
|
if len(value) == 0 {
|
||||||
return field.Name, nil, nil
|
return field.Name, nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ func TestParseSegments(t *testing.T) {
|
|||||||
input: "",
|
input: "",
|
||||||
expect: []string{},
|
expect: []string{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: " ",
|
||||||
|
expect: []string{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: ",",
|
input: ",",
|
||||||
expect: []string{""},
|
expect: []string{""},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type (
|
|||||||
recursiveValuer node
|
recursiveValuer node
|
||||||
)
|
)
|
||||||
|
|
||||||
// Value gets the value assciated with the given key from mv.
|
// Value gets the value associated with the given key from mv.
|
||||||
func (mv mapValuer) Value(key string) (any, bool) {
|
func (mv mapValuer) Value(key string) (any, bool) {
|
||||||
v, ok := mv[key]
|
v, ok := mv[key]
|
||||||
return v, ok
|
return v, ok
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build fuzz
|
//go:build fuzz
|
||||||
// +build fuzz
|
|
||||||
|
|
||||||
package mr
|
package mr
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package proc
|
package proc
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build debug
|
//go:build debug
|
||||||
// +build debug
|
|
||||||
|
|
||||||
package search
|
package search
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !linux
|
//go:build !linux
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package stat
|
package stat
|
||||||
|
|
||||||
|
|||||||
@@ -278,10 +278,8 @@ func runningInUserNS() bool {
|
|||||||
var a, b, c int64
|
var a, b, c int64
|
||||||
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
|
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
|
||||||
|
|
||||||
/*
|
// We assume we are in the initial user namespace if we have a full
|
||||||
* We assume we are in the initial user namespace if we have a full
|
// range - 4294967295 uids starting at uid 0.
|
||||||
* range - 4294967295 uids starting at uid 0.
|
|
||||||
*/
|
|
||||||
if a == 0 && b == 0 && c == 4294967295 {
|
if a == 0 && b == 0 && c == 4294967295 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !linux
|
//go:build !linux
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetrics(t *testing.T) {
|
func TestMetrics(t *testing.T) {
|
||||||
logx.Disable()
|
|
||||||
DisableLog()
|
DisableLog()
|
||||||
defer logEnabled.Set(true)
|
defer logEnabled.Set(true)
|
||||||
|
|
||||||
|
|||||||
64
core/stat/usage_test.go
Normal file
64
core/stat/usage_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package stat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBToMb(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
bytes uint64
|
||||||
|
expected float32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test 1: Convert 0 bytes to MB",
|
||||||
|
bytes: 0,
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 2: Convert 1048576 bytes to MB",
|
||||||
|
bytes: 1048576,
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 3: Convert 2097152 bytes to MB",
|
||||||
|
bytes: 2097152,
|
||||||
|
expected: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := bToMb(test.bytes)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintUsage(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := logx.NewWriter(&buf)
|
||||||
|
old := logx.Reset()
|
||||||
|
logx.SetWriter(writer)
|
||||||
|
defer logx.SetWriter(old)
|
||||||
|
|
||||||
|
printUsage()
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
assert.Contains(t, output, "CPU:")
|
||||||
|
assert.Contains(t, output, "MEMORY:")
|
||||||
|
assert.Contains(t, output, "Alloc=")
|
||||||
|
assert.Contains(t, output, "TotalAlloc=")
|
||||||
|
assert.Contains(t, output, "Sys=")
|
||||||
|
assert.Contains(t, output, "NumGC=")
|
||||||
|
|
||||||
|
lines := strings.Split(output, "\n")
|
||||||
|
assert.Len(t, lines, 2)
|
||||||
|
fields := strings.Split(lines[0], ", ")
|
||||||
|
assert.Len(t, fields, 5)
|
||||||
|
}
|
||||||
12
core/stores/cache/cache_test.go
vendored
12
core/stores/cache/cache_test.go
vendored
@@ -112,12 +112,8 @@ func (mc *mockedNode) TakeWithExpireCtx(ctx context.Context, val any, key string
|
|||||||
func TestCache_SetDel(t *testing.T) {
|
func TestCache_SetDel(t *testing.T) {
|
||||||
t.Run("test set del", func(t *testing.T) {
|
t.Run("test set del", func(t *testing.T) {
|
||||||
const total = 1000
|
const total = 1000
|
||||||
r1, clean1, err := redistest.CreateRedis()
|
r1 := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
r2 := redistest.CreateRedis(t)
|
||||||
defer clean1()
|
|
||||||
r2, clean2, err := redistest.CreateRedis()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean2()
|
|
||||||
conf := ClusterConf{
|
conf := ClusterConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
@@ -193,9 +189,7 @@ 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 := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
conf := ClusterConf{
|
conf := ClusterConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
|
|||||||
44
core/stores/cache/cachenode_test.go
vendored
44
core/stores/cache/cachenode_test.go
vendored
@@ -34,10 +34,8 @@ func init() {
|
|||||||
|
|
||||||
func TestCacheNode_DelCache(t *testing.T) {
|
func TestCacheNode_DelCache(t *testing.T) {
|
||||||
t.Run("del cache", func(t *testing.T) {
|
t.Run("del cache", func(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
store.Type = redis.ClusterType
|
store.Type = redis.ClusterType
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -84,9 +82,7 @@ func TestCacheNode_DelCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
|
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
store.Type = redis.ClusterType
|
store.Type = redis.ClusterType
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
@@ -122,9 +118,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_SetWithExpire(t *testing.T) {
|
func TestCacheNode_SetWithExpire(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -139,14 +133,12 @@ func TestCacheNode_SetWithExpire(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_Take(t *testing.T) {
|
func TestCacheNode_Take(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := NewNode(store, syncx.NewSingleFlight(), NewStat("any"), errTestNotFound,
|
cn := NewNode(store, syncx.NewSingleFlight(), NewStat("any"), errTestNotFound,
|
||||||
WithExpiry(time.Second), WithNotFoundExpiry(time.Second))
|
WithExpiry(time.Second), WithNotFoundExpiry(time.Second))
|
||||||
var str string
|
var str string
|
||||||
err = cn.Take(&str, "any", func(v any) error {
|
err := cn.Take(&str, "any", func(v any) error {
|
||||||
*v.(*string) = "value"
|
*v.(*string) = "value"
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -174,9 +166,7 @@ func TestCacheNode_TakeBadRedis(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_TakeNotFound(t *testing.T) {
|
func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -188,7 +178,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
|||||||
errNotFound: errTestNotFound,
|
errNotFound: errTestNotFound,
|
||||||
}
|
}
|
||||||
var str string
|
var str string
|
||||||
err = cn.Take(&str, "any", func(v any) error {
|
err := cn.Take(&str, "any", func(v any) error {
|
||||||
return errTestNotFound
|
return errTestNotFound
|
||||||
})
|
})
|
||||||
assert.True(t, cn.IsNotFound(err))
|
assert.True(t, cn.IsNotFound(err))
|
||||||
@@ -213,9 +203,7 @@ func TestCacheNode_TakeNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
|
func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.NoError(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -228,7 +216,7 @@ func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
err = cn.Take(&str, "any", func(v any) error {
|
err := cn.Take(&str, "any", func(v any) error {
|
||||||
store.Set("any", "foo")
|
store.Set("any", "foo")
|
||||||
return errTestNotFound
|
return errTestNotFound
|
||||||
})
|
})
|
||||||
@@ -242,9 +230,7 @@ func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_TakeWithExpire(t *testing.T) {
|
func TestCacheNode_TakeWithExpire(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -256,7 +242,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
|
|||||||
errNotFound: errors.New("any"),
|
errNotFound: errors.New("any"),
|
||||||
}
|
}
|
||||||
var str string
|
var str string
|
||||||
err = cn.TakeWithExpire(&str, "any", func(v any, expire time.Duration) error {
|
err := cn.TakeWithExpire(&str, "any", func(v any, expire time.Duration) error {
|
||||||
*v.(*string) = "value"
|
*v.(*string) = "value"
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -269,9 +255,7 @@ func TestCacheNode_TakeWithExpire(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_String(t *testing.T) {
|
func TestCacheNode_String(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
@@ -286,9 +270,7 @@ func TestCacheNode_String(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheValueWithBigInt(t *testing.T) {
|
func TestCacheValueWithBigInt(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: store,
|
rds: store,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// imports the driver, don't remove this comment, golint requires.
|
// imports the driver, don't remove this comment, golint requires.
|
||||||
_ "github.com/jackc/pgx/v5"
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package redis
|
package redis
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrEmptyHost is an error that indicates no redis host is set.
|
// ErrEmptyHost is an error that indicates no redis host is set.
|
||||||
@@ -9,23 +12,24 @@ var (
|
|||||||
ErrEmptyType = errors.New("empty redis type")
|
ErrEmptyType = errors.New("empty redis type")
|
||||||
// ErrEmptyKey is an error that indicates no redis key is set.
|
// ErrEmptyKey is an error that indicates no redis key is set.
|
||||||
ErrEmptyKey = errors.New("empty redis key")
|
ErrEmptyKey = errors.New("empty redis key")
|
||||||
// ErrPing is an error that indicates ping failed.
|
|
||||||
ErrPing = errors.New("ping redis failed")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A RedisConf is a redis config.
|
// A RedisConf is a redis config.
|
||||||
RedisConf struct {
|
RedisConf struct {
|
||||||
Host string
|
Host string
|
||||||
Type string `json:",default=node,options=node|cluster"`
|
Type string `json:",default=node,options=node|cluster"`
|
||||||
Pass string `json:",optional"`
|
Pass string `json:",optional"`
|
||||||
Tls bool `json:",optional"`
|
Tls bool `json:",optional"`
|
||||||
|
NonBlock bool `json:",default=true"`
|
||||||
|
// PingTimeout is the timeout for ping redis.
|
||||||
|
PingTimeout time.Duration `json:",default=1s"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// A RedisKeyConf is a redis config with key.
|
// A RedisKeyConf is a redis config with key.
|
||||||
RedisKeyConf struct {
|
RedisKeyConf struct {
|
||||||
RedisConf
|
RedisConf
|
||||||
Key string `json:",optional"`
|
Key string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
red "github.com/go-redis/redis/v8"
|
red "github.com/go-redis/redis/v8"
|
||||||
"github.com/zeromicro/go-zero/core/breaker"
|
"github.com/zeromicro/go-zero/core/breaker"
|
||||||
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
"github.com/zeromicro/go-zero/core/mapping"
|
"github.com/zeromicro/go-zero/core/mapping"
|
||||||
"github.com/zeromicro/go-zero/core/syncx"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
)
|
)
|
||||||
@@ -25,6 +26,7 @@ const (
|
|||||||
blockingQueryTimeout = 5 * time.Second
|
blockingQueryTimeout = 5 * time.Second
|
||||||
readWriteTimeout = 2 * time.Second
|
readWriteTimeout = 2 * time.Second
|
||||||
defaultSlowThreshold = time.Millisecond * 100
|
defaultSlowThreshold = time.Millisecond * 100
|
||||||
|
defaultPingTimeout = time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -51,11 +53,12 @@ type (
|
|||||||
|
|
||||||
// Redis defines a redis node/cluster. It is thread-safe.
|
// Redis defines a redis node/cluster. It is thread-safe.
|
||||||
Redis struct {
|
Redis struct {
|
||||||
Addr string
|
Addr string
|
||||||
Type string
|
Type string
|
||||||
Pass string
|
Pass string
|
||||||
tls bool
|
tls bool
|
||||||
brk breaker.Breaker
|
brk breaker.Breaker
|
||||||
|
hooks []red.Hook
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedisNode interface represents a redis node.
|
// RedisNode interface represents a redis node.
|
||||||
@@ -84,6 +87,8 @@ type (
|
|||||||
FloatCmd = red.FloatCmd
|
FloatCmd = red.FloatCmd
|
||||||
// StringCmd is an alias of redis.StringCmd.
|
// StringCmd is an alias of redis.StringCmd.
|
||||||
StringCmd = red.StringCmd
|
StringCmd = red.StringCmd
|
||||||
|
// Script is an alias of redis.Script.
|
||||||
|
Script = red.Script
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns a Redis with given options.
|
// New returns a Redis with given options.
|
||||||
@@ -119,8 +124,10 @@ func NewRedis(conf RedisConf, opts ...Option) (*Redis, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rds := newRedis(conf.Host, opts...)
|
rds := newRedis(conf.Host, opts...)
|
||||||
if !rds.Ping() {
|
if !conf.NonBlock {
|
||||||
return nil, ErrPing
|
if err := rds.checkConnection(conf.PingTimeout); err != nil {
|
||||||
|
return nil, errorx.Wrap(err, fmt.Sprintf("redis connect error, addr: %s", conf.Host))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rds, nil
|
return rds, nil
|
||||||
@@ -140,6 +147,11 @@ func newRedis(addr string, opts ...Option) *Redis {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewScript returns a new Script instance.
|
||||||
|
func NewScript(script string) *Script {
|
||||||
|
return red.NewScript(script)
|
||||||
|
}
|
||||||
|
|
||||||
// BitCount is redis bitcount command implementation.
|
// BitCount is redis bitcount command implementation.
|
||||||
func (s *Redis) BitCount(key string, start, end int64) (int64, error) {
|
func (s *Redis) BitCount(key string, start, end int64) (int64, error) {
|
||||||
return s.BitCountCtx(context.Background(), key, start, end)
|
return s.BitCountCtx(context.Background(), key, start, end)
|
||||||
@@ -832,12 +844,12 @@ func (s *Redis) HincrbyCtx(ctx context.Context, key, field string, increment int
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HincrbyFloat is the implementation of redis hincrby command.
|
// HincrbyFloat is the implementation of redis hincrbyfloat command.
|
||||||
func (s *Redis) HincrbyFloat(key, field string, increment float64) (float64, error) {
|
func (s *Redis) HincrbyFloat(key, field string, increment float64) (float64, error) {
|
||||||
return s.HincrbyFloatCtx(context.Background(), key, field, increment)
|
return s.HincrbyFloatCtx(context.Background(), key, field, increment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HincrbyFloatCtx is the implementation of redis hincrby command.
|
// HincrbyFloatCtx is the implementation of redis hincrbyfloat command.
|
||||||
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (val float64, err error) {
|
func (s *Redis) HincrbyFloatCtx(ctx context.Context, key, field string, increment float64) (val float64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
@@ -1065,12 +1077,12 @@ func (s *Redis) IncrbyCtx(ctx context.Context, key string, increment int64) (val
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncrbyFloat is the implementation of redis incrby command.
|
// IncrbyFloat is the implementation of redis hincrbyfloat command.
|
||||||
func (s *Redis) IncrbyFloat(key string, increment float64) (float64, error) {
|
func (s *Redis) IncrbyFloat(key string, increment float64) (float64, error) {
|
||||||
return s.IncrbyFloatCtx(context.Background(), key, increment)
|
return s.IncrbyFloatCtx(context.Background(), key, increment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncrbyFloatCtx is the implementation of redis incrby command.
|
// IncrbyFloatCtx is the implementation of redis hincrbyfloat command.
|
||||||
func (s *Redis) IncrbyFloatCtx(ctx context.Context, key string, increment float64) (val float64, err error) {
|
func (s *Redis) IncrbyFloatCtx(ctx context.Context, key string, increment float64) (val float64, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
@@ -1170,6 +1182,26 @@ func (s *Redis) LpopCtx(ctx context.Context, key string) (val string, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LpopCount is the implementation of redis lpopCount command.
|
||||||
|
func (s *Redis) LpopCount(key string, count int) ([]string, error) {
|
||||||
|
return s.LpopCountCtx(context.Background(), key, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LpopCountCtx is the implementation of redis lpopCount command.
|
||||||
|
func (s *Redis) LpopCountCtx(ctx context.Context, key string, count int) (val []string, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.LPopCount(ctx, key, count).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Lpush is the implementation of redis lpush command.
|
// Lpush is the implementation of redis lpush command.
|
||||||
func (s *Redis) Lpush(key string, values ...any) (int, error) {
|
func (s *Redis) Lpush(key string, values ...any) (int, error) {
|
||||||
return s.LpushCtx(context.Background(), key, values...)
|
return s.LpushCtx(context.Background(), key, values...)
|
||||||
@@ -1432,6 +1464,26 @@ func (s *Redis) RpopCtx(ctx context.Context, key string) (val string, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RpopCount is the implementation of redis rpopCount command.
|
||||||
|
func (s *Redis) RpopCount(key string, count int) ([]string, error) {
|
||||||
|
return s.RpopCountCtx(context.Background(), key, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RpopCountCtx is the implementation of redis rpopCount command.
|
||||||
|
func (s *Redis) RpopCountCtx(ctx context.Context, key string, count int) (val []string, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.RPopCount(ctx, key, count).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Rpush is the implementation of redis rpush command.
|
// Rpush is the implementation of redis rpush command.
|
||||||
func (s *Redis) Rpush(key string, values ...any) (int, error) {
|
func (s *Redis) Rpush(key string, values ...any) (int, error) {
|
||||||
return s.RpushCtx(context.Background(), key, values...)
|
return s.RpushCtx(context.Background(), key, values...)
|
||||||
@@ -1585,6 +1637,25 @@ func (s *Redis) ScriptLoadCtx(ctx context.Context, script string) (string, error
|
|||||||
return conn.ScriptLoad(ctx, script).Result()
|
return conn.ScriptLoad(ctx, script).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScriptRun is the implementation of *redis.Script run command.
|
||||||
|
func (s *Redis) ScriptRun(script *Script, keys []string, args ...any) (any, error) {
|
||||||
|
return s.ScriptRunCtx(context.Background(), script, keys, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptRunCtx is the implementation of *redis.Script run command.
|
||||||
|
func (s *Redis) ScriptRunCtx(ctx context.Context, script *Script, keys []string, args ...any) (val any, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = script.Run(ctx, conn, keys, args...).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Set is the implementation of redis set command.
|
// Set is the implementation of redis set command.
|
||||||
func (s *Redis) Set(key, value string) error {
|
func (s *Redis) Set(key, value string) error {
|
||||||
return s.SetCtx(context.Background(), key, value)
|
return s.SetCtx(context.Background(), key, value)
|
||||||
@@ -2729,6 +2800,23 @@ func (s *Redis) ZunionstoreCtx(ctx context.Context, dest string, store *ZStore)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Redis) checkConnection(pingTimeout time.Duration) error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := defaultPingTimeout
|
||||||
|
if pingTimeout > 0 {
|
||||||
|
timeout = pingTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return conn.Ping(ctx).Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Cluster customizes the given Redis as a cluster.
|
// Cluster customizes the given Redis as a cluster.
|
||||||
func Cluster() Option {
|
func Cluster() Option {
|
||||||
return func(r *Redis) {
|
return func(r *Redis) {
|
||||||
@@ -2755,6 +2843,14 @@ func WithTLS() Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withHook customizes the given Redis with given hook, only for private use now,
|
||||||
|
// maybe expose later.
|
||||||
|
func withHook(hook red.Hook) Option {
|
||||||
|
return func(r *Redis) {
|
||||||
|
r.hooks = append(r.hooks, hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func acceptable(err error) bool {
|
func acceptable(err error) bool {
|
||||||
return err == nil || err == red.Nil || err == context.Canceled
|
return err == nil || err == red.Nil || err == context.Canceled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type myHook struct {
|
||||||
|
red.Hook
|
||||||
|
includePing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ red.Hook = myHook{}
|
||||||
|
|
||||||
|
func (m myHook) BeforeProcess(ctx context.Context, cmd red.Cmder) (context.Context, error) {
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myHook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
|
||||||
|
// skip ping cmd
|
||||||
|
if cmd.Name() == "ping" && !m.includePing {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("hook error")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewRedis(t *testing.T) {
|
func TestNewRedis(t *testing.T) {
|
||||||
r1, err := miniredis.Run()
|
r1, err := miniredis.Run()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -126,6 +145,31 @@ func TestNewRedis(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedis_NonBlock(t *testing.T) {
|
||||||
|
logx.Disable()
|
||||||
|
|
||||||
|
t.Run("nonBlock true", func(t *testing.T) {
|
||||||
|
s := miniredis.RunT(t)
|
||||||
|
// use hook to simulate redis ping error
|
||||||
|
_, err := NewRedis(RedisConf{
|
||||||
|
Host: s.Addr(),
|
||||||
|
NonBlock: true,
|
||||||
|
Type: NodeType,
|
||||||
|
}, withHook(myHook{includePing: true}))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nonBlock false", func(t *testing.T) {
|
||||||
|
s := miniredis.RunT(t)
|
||||||
|
_, err := NewRedis(RedisConf{
|
||||||
|
Host: s.Addr(),
|
||||||
|
NonBlock: false,
|
||||||
|
Type: NodeType,
|
||||||
|
}, withHook(myHook{includePing: true}))
|
||||||
|
assert.ErrorContains(t, err, "redis connect error")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedis_Decr(t *testing.T) {
|
func TestRedis_Decr(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
_, err := New(client.Addr, badType()).Decr("a")
|
_, err := New(client.Addr, badType()).Decr("a")
|
||||||
@@ -196,6 +240,24 @@ func TestRedis_Eval(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedis_ScriptRun(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
sc := NewScript(`redis.call("EXISTS", KEYS[1])`)
|
||||||
|
sc2 := NewScript(`return redis.call("EXISTS", KEYS[1])`)
|
||||||
|
_, err := New(client.Addr, badType()).ScriptRun(sc, []string{"notexist"})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = client.ScriptRun(sc, []string{"notexist"})
|
||||||
|
assert.Equal(t, Nil, err)
|
||||||
|
err = client.Set("key1", "value1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = client.ScriptRun(sc, []string{"key1"})
|
||||||
|
assert.Equal(t, Nil, err)
|
||||||
|
val, err := client.ScriptRun(sc2, []string{"key1"})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(1), val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedis_GeoHash(t *testing.T) {
|
func TestRedis_GeoHash(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
_, err := client.GeoHash("parent", "child1", "child2")
|
_, err := client.GeoHash("parent", "child1", "child2")
|
||||||
@@ -507,6 +569,14 @@ func TestRedis_List(t *testing.T) {
|
|||||||
vals, err = client.Lrange("key", 0, 10)
|
vals, err = client.Lrange("key", 0, 10)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value2", "value3"}, vals)
|
assert.EqualValues(t, []string{"value2", "value3"}, vals)
|
||||||
|
vals, err = client.LpopCount("key", 2)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.EqualValues(t, []string{"value2", "value3"}, vals)
|
||||||
|
_, err = client.Lpush("key", "value1", "value2")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
vals, err = client.RpopCount("key", 4)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.EqualValues(t, []string{"value1", "value2"}, vals)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -523,6 +593,34 @@ func TestRedis_List(t *testing.T) {
|
|||||||
|
|
||||||
_, err = client.Rpush("key", "value3", "value4")
|
_, err = client.Rpush("key", "value3", "value4")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.LpopCount("key", 2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.RpopCount("key", 2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("list redis type error", func(t *testing.T) {
|
||||||
|
runOnRedisWithError(t, func(client *Redis) {
|
||||||
|
client.Type = "nil"
|
||||||
|
_, err := client.Llen("key")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.Lpush("key", "value1", "value2")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.Lrem("key", 2, "value1")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.Rpush("key", "value3", "value4")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.LpopCount("key", 2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
_, err = client.RpopCount("key", 2)
|
||||||
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func CreateBlockingNode(r *Redis) (ClosableNode, error) {
|
|||||||
return &clientBridge{client}, nil
|
return &clientBridge{client}, nil
|
||||||
case ClusterType:
|
case ClusterType:
|
||||||
client := red.NewClusterClient(&red.ClusterOptions{
|
client := red.NewClusterClient(&red.ClusterOptions{
|
||||||
Addrs: []string{r.Addr},
|
Addrs: splitClusterAddrs(r.Addr),
|
||||||
Password: r.Pass,
|
Password: r.Pass,
|
||||||
MaxRetries: maxRetries,
|
MaxRetries: maxRetries,
|
||||||
PoolSize: 1,
|
PoolSize: 1,
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ func getClient(r *Redis) (*red.Client, error) {
|
|||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
})
|
})
|
||||||
store.AddHook(durationHook)
|
store.AddHook(durationHook)
|
||||||
|
for _, hook := range r.hooks {
|
||||||
|
store.AddHook(hook)
|
||||||
|
}
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
red "github.com/go-redis/redis/v8"
|
red "github.com/go-redis/redis/v8"
|
||||||
"github.com/zeromicro/go-zero/core/syncx"
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const addrSep = ","
|
||||||
|
|
||||||
var clusterManager = syncx.NewResourceManager()
|
var clusterManager = syncx.NewResourceManager()
|
||||||
|
|
||||||
func getCluster(r *Redis) (*red.ClusterClient, error) {
|
func getCluster(r *Redis) (*red.ClusterClient, error) {
|
||||||
@@ -19,13 +22,16 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
store := red.NewClusterClient(&red.ClusterOptions{
|
store := red.NewClusterClient(&red.ClusterOptions{
|
||||||
Addrs: []string{r.Addr},
|
Addrs: splitClusterAddrs(r.Addr),
|
||||||
Password: r.Pass,
|
Password: r.Pass,
|
||||||
MaxRetries: maxRetries,
|
MaxRetries: maxRetries,
|
||||||
MinIdleConns: idleConns,
|
MinIdleConns: idleConns,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
})
|
})
|
||||||
store.AddHook(durationHook)
|
store.AddHook(durationHook)
|
||||||
|
for _, hook := range r.hooks {
|
||||||
|
store.AddHook(hook)
|
||||||
|
}
|
||||||
|
|
||||||
return store, nil
|
return store, nil
|
||||||
})
|
})
|
||||||
@@ -35,3 +41,18 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
|
|||||||
|
|
||||||
return val.(*red.ClusterClient), nil
|
return val.(*red.ClusterClient), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitClusterAddrs(addr string) []string {
|
||||||
|
addrs := strings.Split(addr, addrSep)
|
||||||
|
unique := make(map[string]struct{})
|
||||||
|
for _, each := range addrs {
|
||||||
|
unique[strings.TrimSpace(each)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs = addrs[:0]
|
||||||
|
for k := range unique {
|
||||||
|
addrs = append(addrs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|||||||
43
core/stores/redis/redisclustermanager_test.go
Normal file
43
core/stores/redis/redisclustermanager_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitClusterAddrs(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty input",
|
||||||
|
input: "",
|
||||||
|
expected: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single address",
|
||||||
|
input: "127.0.0.1:8000",
|
||||||
|
expected: []string{"127.0.0.1:8000"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple addresses with duplicates",
|
||||||
|
input: "127.0.0.1:8000,127.0.0.1:8001, 127.0.0.1:8000",
|
||||||
|
expected: []string{"127.0.0.1:8000", "127.0.0.1:8001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple addresses without duplicates",
|
||||||
|
input: "127.0.0.1:8000, 127.0.0.1:8001, 127.0.0.1:8002",
|
||||||
|
expected: []string{"127.0.0.1:8000", "127.0.0.1:8001", "127.0.0.1:8002"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.ElementsMatch(t, tc.expected, splitClusterAddrs(tc.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,20 @@ const (
|
|||||||
randomLen = 16
|
randomLen = 16
|
||||||
tolerance = 500 // milliseconds
|
tolerance = 500 // milliseconds
|
||||||
millisPerSecond = 1000
|
millisPerSecond = 1000
|
||||||
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lockScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
||||||
return "OK"
|
return "OK"
|
||||||
else
|
else
|
||||||
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
||||||
end`
|
end`)
|
||||||
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
|
delScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
return redis.call("DEL", KEYS[1])
|
return redis.call("DEL", KEYS[1])
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end`
|
end`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// A RedisLock is a redis lock.
|
// A RedisLock is a redis lock.
|
||||||
@@ -59,7 +62,7 @@ func (rl *RedisLock) Acquire() (bool, error) {
|
|||||||
// AcquireCtx acquires the lock with the given ctx.
|
// AcquireCtx acquires the lock with the given ctx.
|
||||||
func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
|
func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
|
||||||
seconds := atomic.LoadUint32(&rl.seconds)
|
seconds := atomic.LoadUint32(&rl.seconds)
|
||||||
resp, err := rl.store.EvalCtx(ctx, lockCommand, []string{rl.key}, []string{
|
resp, err := rl.store.ScriptRunCtx(ctx, lockScript, []string{rl.key}, []string{
|
||||||
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
|
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
|
||||||
})
|
})
|
||||||
if err == red.Nil {
|
if err == red.Nil {
|
||||||
@@ -87,7 +90,7 @@ func (rl *RedisLock) Release() (bool, error) {
|
|||||||
|
|
||||||
// ReleaseCtx releases the lock with the given ctx.
|
// ReleaseCtx releases the lock with the given ctx.
|
||||||
func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
|
func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
|
||||||
resp, err := rl.store.EvalCtx(ctx, delCommand, []string{rl.key}, []string{rl.id})
|
resp, err := rl.store.ScriptRunCtx(ctx, delScript, []string{rl.key}, []string{rl.id})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,20 @@
|
|||||||
package redistest
|
package redistest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/zeromicro/go-zero/core/lang"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateRedis returns an in process redis.Redis.
|
// CreateRedis returns an in process redis.Redis.
|
||||||
func CreateRedis() (r *redis.Redis, clean func(), err error) {
|
func CreateRedis(t *testing.T) *redis.Redis {
|
||||||
mr, err := miniredis.Run()
|
r, _ := CreateRedisWithClean(t)
|
||||||
if err != nil {
|
return r
|
||||||
return nil, nil, err
|
}
|
||||||
}
|
|
||||||
|
// CreateRedisWithClean returns an in process redis.Redis and a clean function.
|
||||||
return redis.New(mr.Addr()), func() {
|
func CreateRedisWithClean(t *testing.T) (r *redis.Redis, clean func()) {
|
||||||
ch := make(chan lang.PlaceholderType)
|
mr := miniredis.RunT(t)
|
||||||
|
return redis.New(mr.Addr()), mr.Close
|
||||||
go func() {
|
|
||||||
mr.Close()
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,11 @@ func init() {
|
|||||||
|
|
||||||
func TestCachedConn_GetCache(t *testing.T) {
|
func TestCachedConn_GetCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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)
|
||||||
r.Set("any", `"value"`)
|
r.Set("any", `"value"`)
|
||||||
err = c.GetCache("any", &value)
|
err = c.GetCache("any", &value)
|
||||||
@@ -49,15 +47,13 @@ func TestCachedConn_GetCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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++ {
|
||||||
var str string
|
var str string
|
||||||
err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
|
err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
|
||||||
*v.(*string) = "zero"
|
*v.(*string) = "zero"
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -72,9 +68,7 @@ func TestStat(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
||||||
{
|
{
|
||||||
@@ -87,7 +81,7 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
|||||||
}, cache.WithExpiry(time.Second*10))
|
}, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
err = c.QueryRowIndex(&str, "index", func(s any) string {
|
err := c.QueryRowIndex(&str, "index", func(s any) string {
|
||||||
return fmt.Sprintf("%s/1234", s)
|
return fmt.Sprintf("%s/1234", s)
|
||||||
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
||||||
*v.(*string) = "zero"
|
*v.(*string) = "zero"
|
||||||
@@ -121,16 +115,14 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
r.Set("index", `"primary"`)
|
r.Set("index", `"primary"`)
|
||||||
err = c.QueryRowIndex(&str, "index", func(s any) string {
|
err := c.QueryRowIndex(&str, "index", func(s any) string {
|
||||||
return fmt.Sprintf("%s/1234", s)
|
return fmt.Sprintf("%s/1234", s)
|
||||||
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
||||||
assert.Fail(t, "should not go here")
|
assert.Fail(t, "should not go here")
|
||||||
@@ -211,16 +203,14 @@ func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
|
|||||||
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()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
r.Set("index", test.primaryCache)
|
r.Set("index", test.primaryCache)
|
||||||
err = c.QueryRowIndex(&str, "index", func(s any) string {
|
err := c.QueryRowIndex(&str, "index", func(s any) string {
|
||||||
return fmt.Sprintf("%v/1234", s)
|
return fmt.Sprintf("%v/1234", s)
|
||||||
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
||||||
assert.Fail(t, "should not go here")
|
assert.Fail(t, "should not go here")
|
||||||
@@ -251,16 +241,14 @@ 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()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
r.Set(k, v)
|
r.Set(k, v)
|
||||||
err = c.QueryRowIndex(&str, "index", func(s any) string {
|
err := c.QueryRowIndex(&str, "index", func(s any) string {
|
||||||
return fmt.Sprintf("%s/1234", s)
|
return fmt.Sprintf("%s/1234", s)
|
||||||
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
}, func(conn sqlx.SqlConn, v any) (any, error) {
|
||||||
*v.(*string) = "xin"
|
*v.(*string) = "xin"
|
||||||
@@ -306,15 +294,13 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatDbFails(t *testing.T) {
|
func TestStatDbFails(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
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++ {
|
||||||
var str string
|
var str string
|
||||||
err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
|
err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v any) error {
|
||||||
return errors.New("db failed")
|
return errors.New("db failed")
|
||||||
})
|
})
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
@@ -327,9 +313,7 @@ func TestStatDbFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatFromMemory(t *testing.T) {
|
func TestStatFromMemory(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
@@ -385,9 +369,7 @@ func TestStatFromMemory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRow(t *testing.T) {
|
func TestCachedConnQueryRow(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -397,7 +379,7 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
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 any) error {
|
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
|
||||||
ran = true
|
ran = true
|
||||||
user = value
|
user = value
|
||||||
return nil
|
return nil
|
||||||
@@ -413,9 +395,7 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -426,7 +406,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
var ran bool
|
var ran bool
|
||||||
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 any) error {
|
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
|
||||||
ran = true
|
ran = true
|
||||||
user = value
|
user = value
|
||||||
return nil
|
return nil
|
||||||
@@ -442,9 +422,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRowNotFound(t *testing.T) {
|
func TestQueryRowNotFound(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const key = "user"
|
const key = "user"
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
@@ -452,7 +430,7 @@ func TestQueryRowNotFound(t *testing.T) {
|
|||||||
var ran int
|
var ran int
|
||||||
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 any) error {
|
err := c.QueryRow(&user, key, func(conn sqlx.SqlConn, v any) error {
|
||||||
ran++
|
ran++
|
||||||
return sql.ErrNoRows
|
return sql.ErrNoRows
|
||||||
})
|
})
|
||||||
@@ -462,13 +440,11 @@ func TestQueryRowNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExec(t *testing.T) {
|
func TestCachedConnExec(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
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)
|
||||||
assert.True(t, conn.execValue)
|
assert.True(t, conn.execValue)
|
||||||
}
|
}
|
||||||
@@ -514,26 +490,22 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRows(t *testing.T) {
|
func TestCachedConnQueryRows(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
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'")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, conn.queryRowsValue)
|
assert.True(t, conn.queryRowsValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnTransact(t *testing.T) {
|
func TestCachedConnTransact(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
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
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -541,9 +513,7 @@ func TestCachedConnTransact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRowNoCache(t *testing.T) {
|
func TestQueryRowNoCache(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -557,20 +527,18 @@ func TestQueryRowNoCache(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}}
|
}}
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
err = c.QueryRowNoCache(&user, key)
|
err := c.QueryRowNoCache(&user, key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, value, user)
|
assert.Equal(t, value, user)
|
||||||
assert.True(t, ran)
|
assert.True(t, ran)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewConnWithCache(t *testing.T) {
|
func TestNewConnWithCache(t *testing.T) {
|
||||||
r, clean, err := redistest.CreateRedis()
|
r := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
c := NewConnWithCache(&conn, cache.NewNode(r, singleFlights, stats, sql.ErrNoRows))
|
c := NewConnWithCache(&conn, cache.NewNode(r, singleFlights, stats, sql.ErrNoRows))
|
||||||
_, 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)
|
||||||
assert.True(t, conn.execValue)
|
assert.True(t, conn.execValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,23 @@ func getTaggedFieldValueMap(v reflect.Value) (map[string]any, error) {
|
|||||||
result := make(map[string]any, size)
|
result := make(map[string]any, size)
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
key := parseTagName(rt.Field(i))
|
field := rt.Field(i)
|
||||||
|
if field.Anonymous && mapping.Deref(field.Type).Kind() == reflect.Struct {
|
||||||
|
inner, err := getTaggedFieldValueMap(reflect.Indirect(v).Field(i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range inner {
|
||||||
|
result[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := parseTagName(field)
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return nil, nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
valueField := reflect.Indirect(v).Field(i)
|
valueField := reflect.Indirect(v).Field(i)
|
||||||
@@ -114,7 +128,7 @@ func parseTagName(field reflect.StructField) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options := strings.Split(key, ",")
|
options := strings.Split(key, ",")
|
||||||
return options[0]
|
return strings.TrimSpace(options[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalRow(v any, scanner rowsScanner, strict bool) error {
|
func unmarshalRow(v any, scanner rowsScanner, strict bool) error {
|
||||||
|
|||||||
@@ -1041,6 +1041,127 @@ func TestUnmarshalRowError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnonymousStructPr(t *testing.T) {
|
||||||
|
type Score struct {
|
||||||
|
Discipline string `db:"discipline"`
|
||||||
|
Score uint `db:"score"`
|
||||||
|
}
|
||||||
|
type ClassType struct {
|
||||||
|
Grade sql.NullString `db:"grade"`
|
||||||
|
ClassName *string `db:"class_name"`
|
||||||
|
}
|
||||||
|
type Class struct {
|
||||||
|
*ClassType
|
||||||
|
Score
|
||||||
|
}
|
||||||
|
expect := []*struct {
|
||||||
|
Name string
|
||||||
|
Age int64
|
||||||
|
Grade sql.NullString
|
||||||
|
Discipline string
|
||||||
|
Score uint
|
||||||
|
ClassName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "first",
|
||||||
|
Age: 2,
|
||||||
|
Grade: sql.NullString{
|
||||||
|
String: "",
|
||||||
|
Valid: false,
|
||||||
|
},
|
||||||
|
ClassName: "experimental class",
|
||||||
|
Discipline: "math",
|
||||||
|
Score: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "second",
|
||||||
|
Age: 3,
|
||||||
|
Grade: sql.NullString{
|
||||||
|
String: "grade one",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
ClassName: "class three grade two",
|
||||||
|
Discipline: "chinese",
|
||||||
|
Score: 99,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var value []*struct {
|
||||||
|
Age int64 `db:"age"`
|
||||||
|
Class
|
||||||
|
Name string `db:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{
|
||||||
|
"name",
|
||||||
|
"age",
|
||||||
|
"grade",
|
||||||
|
"discipline",
|
||||||
|
"class_name",
|
||||||
|
"score",
|
||||||
|
}).
|
||||||
|
AddRow("first", 2, nil, "math", "experimental class", 100).
|
||||||
|
AddRow("second", 3, "grade one", "chinese", "class three grade two", 99)
|
||||||
|
mock.ExpectQuery("select (.+) from users where user=?").
|
||||||
|
WithArgs("anyone").WillReturnRows(rs)
|
||||||
|
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, age,grade,discipline,class_name,score from users where user=?",
|
||||||
|
"anyone"))
|
||||||
|
|
||||||
|
for i, each := range expect {
|
||||||
|
assert.Equal(t, each.Name, value[i].Name)
|
||||||
|
assert.Equal(t, each.Age, value[i].Age)
|
||||||
|
assert.Equal(t, each.ClassName, *value[i].Class.ClassName)
|
||||||
|
assert.Equal(t, each.Discipline, value[i].Score.Discipline)
|
||||||
|
assert.Equal(t, each.Score, value[i].Score.Score)
|
||||||
|
assert.Equal(t, each.Grade, value[i].Class.Grade)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnonymousStructPrError(t *testing.T) {
|
||||||
|
type Score struct {
|
||||||
|
Discipline string `db:"discipline"`
|
||||||
|
score uint `db:"score"`
|
||||||
|
}
|
||||||
|
type ClassType struct {
|
||||||
|
Grade sql.NullString `db:"grade"`
|
||||||
|
ClassName *string `db:"class_name"`
|
||||||
|
}
|
||||||
|
type Class struct {
|
||||||
|
*ClassType
|
||||||
|
Score
|
||||||
|
}
|
||||||
|
var value []*struct {
|
||||||
|
Age int64 `db:"age"`
|
||||||
|
Class
|
||||||
|
Name string `db:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{
|
||||||
|
"name",
|
||||||
|
"age",
|
||||||
|
"grade",
|
||||||
|
"discipline",
|
||||||
|
"class_name",
|
||||||
|
"score",
|
||||||
|
}).
|
||||||
|
AddRow("first", 2, nil, "math", "experimental class", 100).
|
||||||
|
AddRow("second", 3, "grade one", "chinese", "class three grade two", 99)
|
||||||
|
mock.ExpectQuery("select (.+) from users where user=?").
|
||||||
|
WithArgs("anyone").WillReturnRows(rs)
|
||||||
|
assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRows(&value, rows, true)
|
||||||
|
}, "select name, age,grade,discipline,class_name,score from users where user=?",
|
||||||
|
"anyone"))
|
||||||
|
if len(value) > 0 {
|
||||||
|
assert.Equal(t, value[0].score, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||||
logx.Disable()
|
logx.Disable()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/trace/tracetest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mockedDatasource = "sqlmock"
|
const mockedDatasource = "sqlmock"
|
||||||
@@ -17,6 +18,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSqlConn(t *testing.T) {
|
func TestSqlConn(t *testing.T) {
|
||||||
|
me := tracetest.NewInMemoryExporter(t)
|
||||||
mock, err := buildConn()
|
mock, err := buildConn()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
mock.ExpectExec("any")
|
mock.ExpectExec("any")
|
||||||
@@ -49,6 +51,7 @@ func TestSqlConn(t *testing.T) {
|
|||||||
assert.NotNil(t, badConn.Transact(func(session Session) error {
|
assert.NotNil(t, badConn.Transact(func(session Session) error {
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
assert.Equal(t, 14, len(me.GetSpans()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConn() (mock sqlmock.Sqlmock, err error) {
|
func buildConn() (mock sqlmock.Sqlmock, err error) {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func transactOnConn(ctx context.Context, conn *sql.DB, b beginnable,
|
|||||||
if e := tx.Rollback(); e != nil {
|
if e := tx.Rollback(); e != nil {
|
||||||
err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e)
|
err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("recoveer from %#v", p)
|
err = fmt.Errorf("recover from %#v", p)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
if e := tx.Rollback(); e != nil {
|
if e := tx.Rollback(); e != nil {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
//go:build go1.18
|
|
||||||
// +build go1.18
|
|
||||||
|
|
||||||
package stringx
|
package stringx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
20
core/trace/tracetest/tracetest.go
Normal file
20
core/trace/tracetest/tracetest.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package tracetest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInMemoryExporter returns a new InMemoryExporter
|
||||||
|
// and sets it as the global for tests.
|
||||||
|
func NewInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter {
|
||||||
|
me := tracetest.NewInMemoryExporter()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
me.Reset()
|
||||||
|
})
|
||||||
|
otel.SetTracerProvider(trace.NewTracerProvider(trace.WithSyncer(me)))
|
||||||
|
return me
|
||||||
|
}
|
||||||
@@ -15,8 +15,10 @@ func TestCompareVersions(t *testing.T) {
|
|||||||
out bool
|
out bool
|
||||||
}{
|
}{
|
||||||
{"1", "1.0.1", ">", false},
|
{"1", "1.0.1", ">", false},
|
||||||
|
{"1.0.1", "1.0", "<", false},
|
||||||
{"1", "0.9.9", ">", true},
|
{"1", "0.9.9", ">", true},
|
||||||
{"1", "1.0-1", "<", true},
|
{"1", "1.0-1", "<", true},
|
||||||
|
{"1", "1.0-1", "!", false},
|
||||||
{"1.0.1", "1-0.1", "<", false},
|
{"1.0.1", "1-0.1", "<", false},
|
||||||
{"1.0.1", "1.0.1", "==", true},
|
{"1.0.1", "1.0.1", "==", true},
|
||||||
{"1.0.1", "1.0.2", "==", false},
|
{"1.0.1", "1.0.2", "==", false},
|
||||||
@@ -37,3 +39,21 @@ func TestCompareVersions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrsToInts(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input []string
|
||||||
|
expected []int64
|
||||||
|
}{
|
||||||
|
{[]string{}, nil},
|
||||||
|
{[]string{"1", "2", "3"}, []int64{1, 2, 3}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
actual := strsToInts(tc.input)
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
core/validation/validator.go
Normal file
7
core/validation/validator.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
// Validator represents a validator.
|
||||||
|
type Validator interface {
|
||||||
|
// Validate validates the value.
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest"
|
"github.com/zeromicro/go-zero/rest"
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
)
|
)
|
||||||
@@ -12,7 +10,6 @@ type (
|
|||||||
GatewayConf struct {
|
GatewayConf struct {
|
||||||
rest.RestConf
|
rest.RestConf
|
||||||
Upstreams []Upstream
|
Upstreams []Upstream
|
||||||
Timeout time.Duration `json:",default=5s"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteMapping is a mapping between a gateway route and an upstream rpc method.
|
// RouteMapping is a mapping between a gateway route and an upstream rpc method.
|
||||||
|
|||||||
@@ -37,52 +37,51 @@ func GetMethods(source grpcurl.DescriptorSource) ([]Method, error) {
|
|||||||
for _, method := range svcMethods {
|
for _, method := range svcMethods {
|
||||||
rpcPath := fmt.Sprintf("%s/%s", svc, method.GetName())
|
rpcPath := fmt.Sprintf("%s/%s", svc, method.GetName())
|
||||||
ext := proto.GetExtension(method.GetMethodOptions(), annotations.E_Http)
|
ext := proto.GetExtension(method.GetMethodOptions(), annotations.E_Http)
|
||||||
if ext == nil {
|
switch rule := ext.(type) {
|
||||||
methods = append(methods, Method{
|
case *annotations.HttpRule:
|
||||||
RpcPath: rpcPath,
|
if rule == nil {
|
||||||
})
|
methods = append(methods, Method{
|
||||||
continue
|
RpcPath: rpcPath,
|
||||||
}
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
httpExt, ok := ext.(*annotations.HttpRule)
|
switch httpRule := rule.GetPattern().(type) {
|
||||||
if !ok {
|
case *annotations.HttpRule_Get:
|
||||||
methods = append(methods, Method{
|
methods = append(methods, Method{
|
||||||
RpcPath: rpcPath,
|
HttpMethod: http.MethodGet,
|
||||||
})
|
HttpPath: adjustHttpPath(httpRule.Get),
|
||||||
continue
|
RpcPath: rpcPath,
|
||||||
}
|
})
|
||||||
|
case *annotations.HttpRule_Post:
|
||||||
switch rule := httpExt.GetPattern().(type) {
|
methods = append(methods, Method{
|
||||||
case *annotations.HttpRule_Get:
|
HttpMethod: http.MethodPost,
|
||||||
methods = append(methods, Method{
|
HttpPath: adjustHttpPath(httpRule.Post),
|
||||||
HttpMethod: http.MethodGet,
|
RpcPath: rpcPath,
|
||||||
HttpPath: adjustHttpPath(rule.Get),
|
})
|
||||||
RpcPath: rpcPath,
|
case *annotations.HttpRule_Put:
|
||||||
})
|
methods = append(methods, Method{
|
||||||
case *annotations.HttpRule_Post:
|
HttpMethod: http.MethodPut,
|
||||||
methods = append(methods, Method{
|
HttpPath: adjustHttpPath(httpRule.Put),
|
||||||
HttpMethod: http.MethodPost,
|
RpcPath: rpcPath,
|
||||||
HttpPath: adjustHttpPath(rule.Post),
|
})
|
||||||
RpcPath: rpcPath,
|
case *annotations.HttpRule_Delete:
|
||||||
})
|
methods = append(methods, Method{
|
||||||
case *annotations.HttpRule_Put:
|
HttpMethod: http.MethodDelete,
|
||||||
methods = append(methods, Method{
|
HttpPath: adjustHttpPath(httpRule.Delete),
|
||||||
HttpMethod: http.MethodPut,
|
RpcPath: rpcPath,
|
||||||
HttpPath: adjustHttpPath(rule.Put),
|
})
|
||||||
RpcPath: rpcPath,
|
case *annotations.HttpRule_Patch:
|
||||||
})
|
methods = append(methods, Method{
|
||||||
case *annotations.HttpRule_Delete:
|
HttpMethod: http.MethodPatch,
|
||||||
methods = append(methods, Method{
|
HttpPath: adjustHttpPath(httpRule.Patch),
|
||||||
HttpMethod: http.MethodDelete,
|
RpcPath: rpcPath,
|
||||||
HttpPath: adjustHttpPath(rule.Delete),
|
})
|
||||||
RpcPath: rpcPath,
|
default:
|
||||||
})
|
methods = append(methods, Method{
|
||||||
case *annotations.HttpRule_Patch:
|
RpcPath: rpcPath,
|
||||||
methods = append(methods, Method{
|
})
|
||||||
HttpMethod: http.MethodPatch,
|
}
|
||||||
HttpPath: adjustHttpPath(rule.Patch),
|
|
||||||
RpcPath: rpcPath,
|
|
||||||
})
|
|
||||||
default:
|
default:
|
||||||
methods = append(methods, Method{
|
methods = append(methods, Method{
|
||||||
RpcPath: rpcPath,
|
RpcPath: rpcPath,
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/fullstorydev/grpcurl"
|
"github.com/fullstorydev/grpcurl"
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/hash"
|
"github.com/zeromicro/go-zero/core/hash"
|
||||||
)
|
)
|
||||||
@@ -75,3 +77,50 @@ func TestGetMethodsWithAnnotations(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, methods)
|
}, methods)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetMethodsBadCases(t *testing.T) {
|
||||||
|
t.Run("no services", func(t *testing.T) {
|
||||||
|
source := &mockDescriptorSource{
|
||||||
|
servicesErr: errors.New("no services"),
|
||||||
|
}
|
||||||
|
_, err := GetMethods(source)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no symbol in services", func(t *testing.T) {
|
||||||
|
source := &mockDescriptorSource{
|
||||||
|
services: []string{"hello.Hello"},
|
||||||
|
symbolErr: errors.New("no symbol"),
|
||||||
|
}
|
||||||
|
_, err := GetMethods(source)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no symbol in services", func(t *testing.T) {
|
||||||
|
source := &mockDescriptorSource{
|
||||||
|
services: []string{"hello.Hello"},
|
||||||
|
symbolErr: errors.New("no symbol"),
|
||||||
|
}
|
||||||
|
_, err := GetMethods(source)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockDescriptorSource struct {
|
||||||
|
symbolDesc desc.Descriptor
|
||||||
|
symbolErr error
|
||||||
|
services []string
|
||||||
|
servicesErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDescriptorSource) AllExtensionsForType(_ string) ([]*desc.FieldDescriptor, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDescriptorSource) FindSymbol(_ string) (desc.Descriptor, error) {
|
||||||
|
return m.symbolDesc, m.symbolErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDescriptorSource) ListServices() ([]string, error) {
|
||||||
|
return m.services, m.servicesErr
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ func MustNewServer(c GatewayConf, opts ...Option) *Server {
|
|||||||
svr := &Server{
|
svr := &Server{
|
||||||
Server: rest.MustNewServer(c.RestConf),
|
Server: rest.MustNewServer(c.RestConf),
|
||||||
upstreams: c.Upstreams,
|
upstreams: c.Upstreams,
|
||||||
timeout: c.Timeout,
|
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(svr)
|
opt(svr)
|
||||||
|
|||||||
34
go.mod
34
go.mod
@@ -4,18 +4,18 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0
|
github.com/alicebob/miniredis/v2 v2.30.1
|
||||||
github.com/fatih/color v1.14.1
|
github.com/fatih/color v1.15.0
|
||||||
github.com/felixge/fgprof v0.9.3
|
github.com/felixge/fgprof v0.9.3
|
||||||
github.com/fullstorydev/grpcurl v1.8.7
|
github.com/fullstorydev/grpcurl v1.8.7
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/go-sql-driver/mysql v1.7.0
|
github.com/go-sql-driver/mysql v1.7.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.3
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/jackc/pgx/v5 v5.3.1
|
github.com/jackc/pgx/v5 v5.3.1
|
||||||
github.com/jhump/protoreflect v1.15.0
|
github.com/jhump/protoreflect v1.15.1
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.14.0
|
||||||
@@ -23,7 +23,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
go.etcd.io/etcd/api/v3 v3.5.7
|
go.etcd.io/etcd/api/v3 v3.5.7
|
||||||
go.etcd.io/etcd/client/v3 v3.5.7
|
go.etcd.io/etcd/client/v3 v3.5.7
|
||||||
go.mongodb.org/mongo-driver v1.11.2
|
go.mongodb.org/mongo-driver v1.11.4
|
||||||
go.opentelemetry.io/otel v1.14.0
|
go.opentelemetry.io/otel v1.14.0
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
||||||
@@ -31,26 +31,27 @@ require (
|
|||||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0
|
go.opentelemetry.io/otel/sdk v1.14.0
|
||||||
go.opentelemetry.io/otel/trace v1.14.0
|
go.opentelemetry.io/otel/trace v1.14.0
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.2
|
||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
golang.org/x/sys v0.5.0
|
golang.org/x/net v0.9.0
|
||||||
|
golang.org/x/sys v0.7.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197
|
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197
|
||||||
google.golang.org/grpc v1.53.0
|
google.golang.org/grpc v1.54.0
|
||||||
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
|
google.golang.org/protobuf v1.30.0
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
k8s.io/api v0.26.2
|
k8s.io/api v0.26.3
|
||||||
k8s.io/apimachinery v0.26.2
|
k8s.io/apimachinery v0.26.3
|
||||||
k8s.io/client-go v0.26.2
|
k8s.io/client-go v0.26.3
|
||||||
k8s.io/utils v0.0.0-20230115233650-391b47cb4029
|
k8s.io/utils v0.0.0-20230115233650-391b47cb4029
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f // indirect
|
github.com/bufbuild/protocompile v0.4.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
@@ -96,7 +97,7 @@ require (
|
|||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.1 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
|
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
||||||
@@ -105,11 +106,10 @@ require (
|
|||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.6.0 // indirect
|
golang.org/x/crypto v0.6.0 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.4.0 // indirect
|
golang.org/x/oauth2 v0.4.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/term v0.5.0 // indirect
|
golang.org/x/term v0.7.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
67
go.sum
67
go.sum
@@ -43,16 +43,16 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
|||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g=
|
||||||
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
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=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f h1:IXSA5gow10s7zIOJfPOpXDtNBWCTA0715BDAhoJBXEs=
|
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||||
github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f/go.mod h1:tleDrpPTlLUVmgnEoN6qBliKWqJaZFJXqZdFjTd+ocU=
|
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@@ -95,8 +95,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
@@ -169,8 +169,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
|||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
@@ -232,8 +233,8 @@ github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+
|
|||||||
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
|
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
|
||||||
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
||||||
github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
|
github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
|
||||||
github.com/jhump/protoreflect v1.15.0 h1:U5T5/2LF0AZQFP9T4W5GfBjBaTruomrKobiR4E+oA/Q=
|
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||||
github.com/jhump/protoreflect v1.15.0/go.mod h1:qww51KYjD2hoCl/ohxw5cK2LSssFczrbO1t8Ld2TENs=
|
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
@@ -379,16 +380,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
|
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
|
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
|
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
|
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
|
||||||
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
|
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
|
||||||
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -417,8 +418,8 @@ go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJP
|
|||||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||||
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
|
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
@@ -499,8 +500,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -569,12 +570,12 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -583,8 +584,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -713,8 +714,8 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
|
|||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -729,8 +730,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -765,12 +766,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
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=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
|
k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
|
||||||
k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
|
k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
|
||||||
k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
|
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
|
||||||
k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
||||||
k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
|
k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s=
|
||||||
k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
|
k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ=
|
||||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||||
|
|||||||
18
readme-cn.md
18
readme-cn.md
@@ -23,7 +23,7 @@
|
|||||||
>
|
>
|
||||||
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
||||||
>
|
>
|
||||||
> `goctl migrate —verbose —version v1.4.3`
|
> `goctl migrate —verbose —version v1.5.0`
|
||||||
|
|
||||||
## 0. go-zero 介绍
|
## 0. go-zero 介绍
|
||||||
|
|
||||||
@@ -119,28 +119,25 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
|||||||
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 `工具` 来解放我们的双手👈
|
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 `工具` 来解放我们的双手👈
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Go 1.15 及之前版本
|
# Go
|
||||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero/tools/goctl@latest
|
|
||||||
|
|
||||||
# Go 1.16 及以后版本
|
|
||||||
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
|
GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||||
|
|
||||||
# For Mac
|
# For Mac
|
||||||
brew install goctl
|
brew install goctl
|
||||||
|
|
||||||
# docker for amd64 architecture
|
# docker for amd64 architecture
|
||||||
docker pull kevinwan/goctl
|
docker pull kevinwan/goctl
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
||||||
|
|
||||||
# docker for arm64 (M1) architecture
|
# docker for arm64 (M1) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
||||||
```
|
```
|
||||||
|
|
||||||
确保 goctl 可执行
|
确保 goctl 可执行
|
||||||
|
|
||||||
2. 快速生成 api 服务
|
2. 快速生成 api 服务
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -301,6 +298,7 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
|||||||
>86. 中国移动上海产业研究院
|
>86. 中国移动上海产业研究院
|
||||||
>87. 天枢数链(浙江)科技有限公司
|
>87. 天枢数链(浙江)科技有限公司
|
||||||
>88. 北京娱人共享智能科技有限公司
|
>88. 北京娱人共享智能科技有限公司
|
||||||
|
>89. 北京数智方科技有限公司
|
||||||
|
|
||||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||||
|
|
||||||
|
|||||||
17
readme.md
17
readme.md
@@ -111,7 +111,7 @@ go install github.com/zeromicro/go-zero/tools/goctl@latest
|
|||||||
```
|
```
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
goctl migrate —verbose —version v1.4.3
|
goctl migrate —verbose —version v1.5.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -127,28 +127,25 @@ goctl migrate —verbose —version v1.4.3
|
|||||||
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
|
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# for Go 1.15 and earlier
|
# for Go
|
||||||
GO111MODULE=on go get -u github.com/zeromicro/go-zero/tools/goctl@latest
|
|
||||||
|
|
||||||
# for Go 1.16 and later
|
|
||||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||||
|
|
||||||
# For Mac
|
# For Mac
|
||||||
brew install goctl
|
brew install goctl
|
||||||
|
|
||||||
# docker for amd64 architecture
|
# docker for amd64 architecture
|
||||||
docker pull kevinwan/goctl
|
docker pull kevinwan/goctl
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl goctl --help
|
||||||
|
|
||||||
# docker for arm64 (M1) architecture
|
# docker for arm64 (M1) architecture
|
||||||
docker pull kevinwan/goctl:latest-arm64
|
docker pull kevinwan/goctl:latest-arm64
|
||||||
# run goctl like
|
# run goctl like
|
||||||
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
docker run --rm -it -v `pwd`:/app kevinwan/goctl:latest-arm64 goctl --help
|
||||||
```
|
```
|
||||||
|
|
||||||
make sure goctl is executable.
|
make sure goctl is executable.
|
||||||
|
|
||||||
3. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
|
3. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@@ -301,21 +301,29 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain.Chai
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ng *engine) start(router httpx.Router) error {
|
func (ng *engine) start(router httpx.Router, opts ...StartOption) error {
|
||||||
if err := ng.bindRoutes(router); err != nil {
|
if err := ng.bindRoutes(router); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure user defined options overwrite default options
|
||||||
|
opts = append([]StartOption{ng.withTimeout()}, opts...)
|
||||||
|
|
||||||
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
|
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
|
||||||
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, ng.withTimeout())
|
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
|
// make sure user defined options overwrite default options
|
||||||
ng.conf.KeyFile, router, func(svr *http.Server) {
|
opts = append([]StartOption{
|
||||||
|
func(svr *http.Server) {
|
||||||
if ng.tlsConfig != nil {
|
if ng.tlsConfig != nil {
|
||||||
svr.TLSConfig = ng.tlsConfig
|
svr.TLSConfig = ng.tlsConfig
|
||||||
}
|
}
|
||||||
}, ng.withTimeout())
|
},
|
||||||
|
}, opts...)
|
||||||
|
|
||||||
|
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
|
||||||
|
ng.conf.KeyFile, router, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ng *engine) use(middleware Middleware) {
|
func (ng *engine) use(middleware Middleware) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -17,18 +18,21 @@ import (
|
|||||||
func TestNewEngine(t *testing.T) {
|
func TestNewEngine(t *testing.T) {
|
||||||
yamls := []string{
|
yamls := []string{
|
||||||
`Name: foo
|
`Name: foo
|
||||||
Port: 54321
|
Host: localhost
|
||||||
|
Port: 0
|
||||||
Middlewares:
|
Middlewares:
|
||||||
Log: false
|
Log: false
|
||||||
`,
|
`,
|
||||||
`Name: foo
|
`Name: foo
|
||||||
Port: 54321
|
Host: localhost
|
||||||
|
Port: 0
|
||||||
CpuThreshold: 500
|
CpuThreshold: 500
|
||||||
Middlewares:
|
Middlewares:
|
||||||
Log: false
|
Log: false
|
||||||
`,
|
`,
|
||||||
`Name: foo
|
`Name: foo
|
||||||
Port: 54321
|
Host: localhost
|
||||||
|
Port: 0
|
||||||
CpuThreshold: 500
|
CpuThreshold: 500
|
||||||
Verbose: true
|
Verbose: true
|
||||||
`,
|
`,
|
||||||
@@ -150,22 +154,29 @@ Verbose: true
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, yaml := range yamls {
|
for _, yaml := range yamls {
|
||||||
|
yaml := yaml
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
var cnf RestConf
|
route := route
|
||||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
|
t.Run(fmt.Sprintf("%s-%v", yaml, route.routes), func(t *testing.T) {
|
||||||
ng := newEngine(cnf)
|
var cnf RestConf
|
||||||
ng.addRoutes(route)
|
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
|
||||||
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
|
ng := newEngine(cnf)
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
ng.addRoutes(route)
|
||||||
next.ServeHTTP(w, r)
|
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotNil(t, ng.start(mockedRouter{}, func(svr *http.Server) {
|
||||||
|
}))
|
||||||
|
|
||||||
|
timeout := time.Second * 3
|
||||||
|
if route.timeout > timeout {
|
||||||
|
timeout = route.timeout
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, timeout, ng.timeout)
|
||||||
})
|
})
|
||||||
assert.NotNil(t, ng.start(mockedRouter{}))
|
|
||||||
timeout := time.Second * 3
|
|
||||||
if route.timeout > timeout {
|
|
||||||
timeout = route.timeout
|
|
||||||
}
|
|
||||||
assert.Equal(t, timeout, ng.timeout)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +351,8 @@ func TestEngine_withTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockedRouter struct{}
|
type mockedRouter struct {
|
||||||
|
}
|
||||||
|
|
||||||
func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
|
func (m mockedRouter) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,8 +379,7 @@ func createTempFile(body []byte) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
err = os.WriteFile(tmpFile.Name(), body, os.ModePerm)
|
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,10 @@ func (tw *timeoutWriter) writeHeaderLocked(code int) {
|
|||||||
func (tw *timeoutWriter) WriteHeader(code int) {
|
func (tw *timeoutWriter) WriteHeader(code int) {
|
||||||
tw.mu.Lock()
|
tw.mu.Lock()
|
||||||
defer tw.mu.Unlock()
|
defer tw.mu.Unlock()
|
||||||
tw.writeHeaderLocked(code)
|
|
||||||
|
if !tw.wroteHeader {
|
||||||
|
tw.writeHeaderLocked(code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkWriteHeaderCode(code int) {
|
func checkWriteHeaderCode(code int) {
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
ztrace "github.com/zeromicro/go-zero/core/trace"
|
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||||
|
"github.com/zeromicro/go-zero/core/trace/tracetest"
|
||||||
"github.com/zeromicro/go-zero/rest/chain"
|
"github.com/zeromicro/go-zero/rest/chain"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
tcodes "go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,6 +57,31 @@ func TestOtelHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTraceHandler(t *testing.T) {
|
||||||
|
me := tracetest.NewInMemoryExporter(t)
|
||||||
|
h := chain.New(TraceHandler("foo", "/")).Then(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
ts := httptest.NewServer(h)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := ts.Client()
|
||||||
|
err := func(ctx context.Context) error {
|
||||||
|
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return res.Body.Close()
|
||||||
|
}(context.Background())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(me.GetSpans()))
|
||||||
|
span := me.GetSpans()[0].Snapshot()
|
||||||
|
assert.Equal(t, sdktrace.Status{
|
||||||
|
Code: tcodes.Unset,
|
||||||
|
}, span.Status())
|
||||||
|
assert.Equal(t, 0, len(span.Events()))
|
||||||
|
assert.Equal(t, 9, len(span.Attributes()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestDontTracingSpan(t *testing.T) {
|
func TestDontTracingSpan(t *testing.T) {
|
||||||
ztrace.StartAgent(ztrace.Config{
|
ztrace.StartAgent(ztrace.Config{
|
||||||
Name: "go-zero-test",
|
Name: "go-zero-test",
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
ztrace "github.com/zeromicro/go-zero/core/trace"
|
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||||
|
"github.com/zeromicro/go-zero/core/trace/tracetest"
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||||
"github.com/zeromicro/go-zero/rest/router"
|
"github.com/zeromicro/go-zero/rest/router"
|
||||||
|
tcodes "go.opentelemetry.io/otel/codes"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@ func TestDoRequest_Moved(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDo(t *testing.T) {
|
func TestDo(t *testing.T) {
|
||||||
|
me := tracetest.NewInMemoryExporter(t)
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Key string `path:"key"`
|
Key string `path:"key"`
|
||||||
Value int `form:"value"`
|
Value int `form:"value"`
|
||||||
@@ -86,6 +90,13 @@ func TestDo(t *testing.T) {
|
|||||||
resp, err := Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
|
resp, err := Do(context.Background(), http.MethodPost, svr.URL+"/nodes/:key", data)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
assert.Equal(t, 1, len(me.GetSpans()))
|
||||||
|
span := me.GetSpans()[0].Snapshot()
|
||||||
|
assert.Equal(t, sdktrace.Status{
|
||||||
|
Code: tcodes.Unset,
|
||||||
|
}, span.Status())
|
||||||
|
assert.Equal(t, 0, len(span.Events()))
|
||||||
|
assert.Equal(t, 7, len(span.Attributes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDo_Ptr(t *testing.T) {
|
func TestDo_Ptr(t *testing.T) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/mapping"
|
"github.com/zeromicro/go-zero/core/mapping"
|
||||||
|
"github.com/zeromicro/go-zero/core/validation"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/encoding"
|
"github.com/zeromicro/go-zero/rest/internal/encoding"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/header"
|
"github.com/zeromicro/go-zero/rest/internal/header"
|
||||||
"github.com/zeromicro/go-zero/rest/pathvar"
|
"github.com/zeromicro/go-zero/rest/pathvar"
|
||||||
@@ -51,7 +52,9 @@ func Parse(r *http.Request, v any) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if val := validator.Load(); val != nil {
|
if valid, ok := v.(validation.Validator); ok {
|
||||||
|
return valid.Validate()
|
||||||
|
} else if val := validator.Load(); val != nil {
|
||||||
return val.(Validator).Validate(r, v)
|
return val.(Validator).Validate(r, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,6 +354,14 @@ func TestParseWithValidatorWithError(t *testing.T) {
|
|||||||
assert.Error(t, Parse(r, &v))
|
assert.Error(t, Parse(r, &v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseWithValidatorRequest(t *testing.T) {
|
||||||
|
SetValidator(mockValidator{})
|
||||||
|
var v mockRequest
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "/a?&age=18", http.NoBody)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Error(t, Parse(r, &v))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkParseRaw(b *testing.B) {
|
func BenchmarkParseRaw(b *testing.B) {
|
||||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
|
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -410,3 +418,15 @@ func (m mockValidator) Validate(r *http.Request, data any) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockRequest struct {
|
||||||
|
Name string `json:"name,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockRequest) Validate() error {
|
||||||
|
if m.Name != "hello" {
|
||||||
|
return errors.New("name is not hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,12 +77,19 @@ func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isOriginAllowed(allows []string, origin string) bool {
|
func isOriginAllowed(allows []string, origin string) bool {
|
||||||
for _, o := range allows {
|
origin = strings.ToLower(origin)
|
||||||
if o == allOrigins {
|
|
||||||
|
for _, allow := range allows {
|
||||||
|
if allow == allOrigins {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(origin, o) {
|
allow = strings.ToLower(allow)
|
||||||
|
if origin == allow {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(origin, "."+allow) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ func TestCorsHandlerWithOrigins(t *testing.T) {
|
|||||||
origins: []string{"http://local", "http://remote"},
|
origins: []string{"http://local", "http://remote"},
|
||||||
reqOrigin: "http://another",
|
reqOrigin: "http://another",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "not safe origin",
|
||||||
|
origins: []string{"safe.com"},
|
||||||
|
reqOrigin: "not-safe.com",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
methods := []string{
|
methods := []string{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package rest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/rest/chain"
|
"github.com/zeromicro/go-zero/rest/chain"
|
||||||
"github.com/zeromicro/go-zero/rest/handler"
|
"github.com/zeromicro/go-zero/rest/handler"
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"github.com/zeromicro/go-zero/rest/internal"
|
||||||
"github.com/zeromicro/go-zero/rest/internal/cors"
|
"github.com/zeromicro/go-zero/rest/internal/cors"
|
||||||
"github.com/zeromicro/go-zero/rest/router"
|
"github.com/zeromicro/go-zero/rest/router"
|
||||||
)
|
)
|
||||||
@@ -19,6 +19,9 @@ type (
|
|||||||
// RunOption defines the method to customize a Server.
|
// RunOption defines the method to customize a Server.
|
||||||
RunOption func(*Server)
|
RunOption func(*Server)
|
||||||
|
|
||||||
|
// StartOption defines the method to customize http server.
|
||||||
|
StartOption = internal.StartOption
|
||||||
|
|
||||||
// A Server is a http server.
|
// A Server is a http server.
|
||||||
Server struct {
|
Server struct {
|
||||||
ngin *engine
|
ngin *engine
|
||||||
@@ -32,7 +35,7 @@ type (
|
|||||||
func MustNewServer(c RestConf, opts ...RunOption) *Server {
|
func MustNewServer(c RestConf, opts ...RunOption) *Server {
|
||||||
server, err := NewServer(c, opts...)
|
server, err := NewServer(c, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logx.Must(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return server
|
return server
|
||||||
@@ -116,6 +119,13 @@ func (s *Server) Start() {
|
|||||||
handleError(s.ngin.start(s.router))
|
handleError(s.ngin.start(s.router))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartWithOpts starts the Server.
|
||||||
|
// Graceful shutdown is enabled by default.
|
||||||
|
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
|
||||||
|
func (s *Server) StartWithOpts(opts ...StartOption) {
|
||||||
|
handleError(s.ngin.start(s.router, opts...))
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the Server.
|
// Stop stops the Server.
|
||||||
func (s *Server) Stop() {
|
func (s *Server) Stop() {
|
||||||
logx.Close()
|
logx.Close()
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ func TestNewServer(t *testing.T) {
|
|||||||
|
|
||||||
const configYaml = `
|
const configYaml = `
|
||||||
Name: foo
|
Name: foo
|
||||||
Port: 54321
|
Host: localhost
|
||||||
|
Port: 0
|
||||||
`
|
`
|
||||||
var cnf RestConf
|
var cnf RestConf
|
||||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
|
||||||
@@ -101,6 +102,23 @@ Port: 54321
|
|||||||
svr.Start()
|
svr.Start()
|
||||||
svr.Stop()
|
svr.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
p := recover()
|
||||||
|
switch v := p.(type) {
|
||||||
|
case error:
|
||||||
|
assert.Equal(t, "foo", v.Error())
|
||||||
|
default:
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
svr.StartWithOpts(func(svr *http.Server) {
|
||||||
|
svr.RegisterOnShutdown(func() {})
|
||||||
|
})
|
||||||
|
svr.Stop()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +587,6 @@ Port: 54321
|
|||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/user/:name",
|
Path: "/user/:name",
|
||||||
Handler: func(writer http.ResponseWriter, request *http.Request) {
|
Handler: func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
|
||||||
var userInfo struct {
|
var userInfo struct {
|
||||||
Name string `path:"name"`
|
Name string `path:"name"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,165 +13,91 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/tools/goctl/api/tsgen"
|
"github.com/zeromicro/go-zero/tools/goctl/api/tsgen"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/validate"
|
"github.com/zeromicro/go-zero/tools/goctl/api/validate"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/config"
|
"github.com/zeromicro/go-zero/tools/goctl/config"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/internal/cobrax"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
"github.com/zeromicro/go-zero/tools/goctl/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Cmd describes an api command.
|
// Cmd describes an api command.
|
||||||
Cmd = &cobra.Command{
|
Cmd = cobrax.NewCommand("api", cobrax.WithRunE(apigen.CreateApiTemplate))
|
||||||
Use: "api",
|
dartCmd = cobrax.NewCommand("dart", cobrax.WithRunE(dartgen.DartCommand))
|
||||||
Short: "Generate api related files",
|
docCmd = cobrax.NewCommand("doc", cobrax.WithRunE(docgen.DocCommand))
|
||||||
RunE: apigen.CreateApiTemplate,
|
formatCmd = cobrax.NewCommand("format", cobrax.WithRunE(format.GoFormatApi))
|
||||||
}
|
goCmd = cobrax.NewCommand("go", cobrax.WithRunE(gogen.GoCommand))
|
||||||
|
newCmd = cobrax.NewCommand("new", cobrax.WithRunE(new.CreateServiceCommand),
|
||||||
dartCmd = &cobra.Command{
|
cobrax.WithArgs(cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)))
|
||||||
Use: "dart",
|
validateCmd = cobrax.NewCommand("validate", cobrax.WithRunE(validate.GoValidateApi))
|
||||||
Short: "Generate dart files for provided api in api file",
|
javaCmd = cobrax.NewCommand("java", cobrax.WithRunE(javagen.JavaCommand), cobrax.WithHidden())
|
||||||
RunE: dartgen.DartCommand,
|
ktCmd = cobrax.NewCommand("kt", cobrax.WithRunE(ktgen.KtCommand))
|
||||||
}
|
pluginCmd = cobrax.NewCommand("plugin", cobrax.WithRunE(plugin.PluginCommand))
|
||||||
|
tsCmd = cobrax.NewCommand("ts", cobrax.WithRunE(tsgen.TsCommand))
|
||||||
docCmd = &cobra.Command{
|
|
||||||
Use: "doc",
|
|
||||||
Short: "Generate doc files",
|
|
||||||
RunE: docgen.DocCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
formatCmd = &cobra.Command{
|
|
||||||
Use: "format",
|
|
||||||
Short: "Format api files",
|
|
||||||
RunE: format.GoFormatApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
goCmd = &cobra.Command{
|
|
||||||
Use: "go",
|
|
||||||
Short: "Generate go files for provided api in api file",
|
|
||||||
RunE: gogen.GoCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
newCmd = &cobra.Command{
|
|
||||||
Use: "new",
|
|
||||||
Short: "Fast create api service",
|
|
||||||
Example: "goctl api new [options] service-name",
|
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return new.CreateServiceCommand(args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
validateCmd = &cobra.Command{
|
|
||||||
Use: "validate",
|
|
||||||
Short: "Validate api file",
|
|
||||||
RunE: validate.GoValidateApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
javaCmd = &cobra.Command{
|
|
||||||
Use: "java",
|
|
||||||
Short: "Generate java files for provided api in api file",
|
|
||||||
Hidden: true,
|
|
||||||
RunE: javagen.JavaCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
ktCmd = &cobra.Command{
|
|
||||||
Use: "kt",
|
|
||||||
Short: "Generate kotlin code for provided api file",
|
|
||||||
RunE: ktgen.KtCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCmd = &cobra.Command{
|
|
||||||
Use: "plugin",
|
|
||||||
Short: "Custom file generator",
|
|
||||||
RunE: plugin.PluginCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
tsCmd = &cobra.Command{
|
|
||||||
Use: "ts",
|
|
||||||
Short: "Generate ts files for provided api in api file",
|
|
||||||
RunE: tsgen.TsCommand,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Cmd.Flags().StringVar(&apigen.VarStringOutput, "o", "", "Output a sample api file")
|
var (
|
||||||
Cmd.Flags().StringVar(&apigen.VarStringHome, "home", "", "The goctl home path of the"+
|
apiCmdFlags = Cmd.Flags()
|
||||||
" template, --home and --remote cannot be set at the same time, if they are, --remote has "+
|
dartCmdFlags = dartCmd.Flags()
|
||||||
"higher priority")
|
docCmdFlags = docCmd.Flags()
|
||||||
Cmd.Flags().StringVar(&apigen.VarStringRemote, "remote", "", "The remote git repo of the"+
|
formatCmdFlags = formatCmd.Flags()
|
||||||
" template, --home and --remote cannot be set at the same time, if they are, --remote has higher"+
|
goCmdFlags = goCmd.Flags()
|
||||||
" priority\nThe git repo directory must be consistent with the"+
|
javaCmdFlags = javaCmd.Flags()
|
||||||
" https://github.com/zeromicro/go-zero-template directory structure")
|
ktCmdFlags = ktCmd.Flags()
|
||||||
Cmd.Flags().StringVar(&apigen.VarStringBranch, "branch", "", "The branch of the "+
|
newCmdFlags = newCmd.Flags()
|
||||||
"remote repo, it does work with --remote")
|
pluginCmdFlags = pluginCmd.Flags()
|
||||||
|
tsCmdFlags = tsCmd.Flags()
|
||||||
|
validateCmdFlags = validateCmd.Flags()
|
||||||
|
)
|
||||||
|
|
||||||
dartCmd.Flags().StringVar(&dartgen.VarStringDir, "dir", "", "The target dir")
|
apiCmdFlags.StringVar(&apigen.VarStringOutput, "o")
|
||||||
dartCmd.Flags().StringVar(&dartgen.VarStringAPI, "api", "", "The api file")
|
apiCmdFlags.StringVar(&apigen.VarStringHome, "home")
|
||||||
dartCmd.Flags().BoolVar(&dartgen.VarStringLegacy, "legacy", false, "Legacy generator for flutter v1")
|
apiCmdFlags.StringVar(&apigen.VarStringRemote, "remote")
|
||||||
dartCmd.Flags().StringVar(&dartgen.VarStringHostname, "hostname", "", "hostname of the server")
|
apiCmdFlags.StringVar(&apigen.VarStringBranch, "branch")
|
||||||
|
|
||||||
docCmd.Flags().StringVar(&docgen.VarStringDir, "dir", "", "The target dir")
|
dartCmdFlags.StringVar(&dartgen.VarStringDir, "dir")
|
||||||
docCmd.Flags().StringVar(&docgen.VarStringOutput, "o", "", "The output markdown directory")
|
dartCmdFlags.StringVar(&dartgen.VarStringAPI, "api")
|
||||||
|
dartCmdFlags.BoolVar(&dartgen.VarStringLegacy, "legacy")
|
||||||
|
dartCmdFlags.StringVar(&dartgen.VarStringHostname, "hostname")
|
||||||
|
dartCmdFlags.StringVar(&dartgen.VarStringScheme, "scheme")
|
||||||
|
|
||||||
formatCmd.Flags().StringVar(&format.VarStringDir, "dir", "", "The format target dir")
|
docCmdFlags.StringVar(&docgen.VarStringDir, "dir")
|
||||||
formatCmd.Flags().BoolVar(&format.VarBoolIgnore, "iu", false, "Ignore update")
|
docCmdFlags.StringVar(&docgen.VarStringOutput, "o")
|
||||||
formatCmd.Flags().BoolVar(&format.VarBoolUseStdin, "stdin", false, "Use stdin to input api"+
|
|
||||||
" doc content, press \"ctrl + d\" to send EOF")
|
|
||||||
formatCmd.Flags().BoolVar(&format.VarBoolSkipCheckDeclare, "declare", false, "Use to skip check "+
|
|
||||||
"api types already declare")
|
|
||||||
|
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringDir, "dir", "", "The target dir")
|
formatCmdFlags.StringVar(&format.VarStringDir, "dir")
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringAPI, "api", "", "The api file")
|
formatCmdFlags.BoolVar(&format.VarBoolIgnore, "iu")
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringHome, "home", "", "The goctl home path of "+
|
formatCmdFlags.BoolVar(&format.VarBoolUseStdin, "stdin")
|
||||||
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
|
formatCmdFlags.BoolVar(&format.VarBoolSkipCheckDeclare, "declare")
|
||||||
"has higher priority")
|
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringRemote, "remote", "", "The remote git repo "+
|
|
||||||
"of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
|
|
||||||
" has higher priority\nThe git repo directory must be consistent with the "+
|
|
||||||
"https://github.com/zeromicro/go-zero-template directory structure")
|
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringBranch, "branch", "", "The branch of "+
|
|
||||||
"the remote repo, it does work with --remote")
|
|
||||||
goCmd.Flags().StringVar(&gogen.VarStringStyle, "style", config.DefaultFormat, "The file naming format,"+
|
|
||||||
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
|
|
||||||
|
|
||||||
javaCmd.Flags().StringVar(&javagen.VarStringDir, "dir", "", "The target dir")
|
goCmdFlags.StringVar(&gogen.VarStringDir, "dir")
|
||||||
javaCmd.Flags().StringVar(&javagen.VarStringAPI, "api", "", "The api file")
|
goCmdFlags.StringVar(&gogen.VarStringAPI, "api")
|
||||||
|
goCmdFlags.StringVar(&gogen.VarStringHome, "home")
|
||||||
|
goCmdFlags.StringVar(&gogen.VarStringRemote, "remote")
|
||||||
|
goCmdFlags.StringVar(&gogen.VarStringBranch, "branch")
|
||||||
|
goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat)
|
||||||
|
|
||||||
ktCmd.Flags().StringVar(&ktgen.VarStringDir, "dir", "", "The target dir")
|
javaCmdFlags.StringVar(&javagen.VarStringDir, "dir")
|
||||||
ktCmd.Flags().StringVar(&ktgen.VarStringAPI, "api", "", "The api file")
|
javaCmdFlags.StringVar(&javagen.VarStringAPI, "api")
|
||||||
ktCmd.Flags().StringVar(&ktgen.VarStringPKG, "pkg", "", "Define package name for kotlin file")
|
|
||||||
|
|
||||||
newCmd.Flags().StringVar(&new.VarStringHome, "home", "", "The goctl home path of "+
|
ktCmdFlags.StringVar(&ktgen.VarStringDir, "dir")
|
||||||
"the template, --home and --remote cannot be set at the same time, if they are, --remote "+
|
ktCmdFlags.StringVar(&ktgen.VarStringAPI, "api")
|
||||||
"has higher priority")
|
ktCmdFlags.StringVar(&ktgen.VarStringPKG, "pkg")
|
||||||
newCmd.Flags().StringVar(&new.VarStringRemote, "remote", "", "The remote git repo "+
|
|
||||||
"of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
|
|
||||||
" has higher priority\n\tThe git repo directory must be consistent with the "+
|
|
||||||
"https://github.com/zeromicro/go-zero-template directory structure")
|
|
||||||
newCmd.Flags().StringVar(&new.VarStringBranch, "branch", "", "The branch of "+
|
|
||||||
"the remote repo, it does work with --remote")
|
|
||||||
newCmd.Flags().StringVar(&new.VarStringStyle, "style", config.DefaultFormat, "The file naming format,"+
|
|
||||||
" see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]")
|
|
||||||
|
|
||||||
pluginCmd.Flags().StringVarP(&plugin.VarStringPlugin, "plugin", "p", "", "The plugin file")
|
newCmdFlags.StringVar(&new.VarStringHome, "home")
|
||||||
pluginCmd.Flags().StringVar(&plugin.VarStringDir, "dir", "", "The target dir")
|
newCmdFlags.StringVar(&new.VarStringRemote, "remote")
|
||||||
pluginCmd.Flags().StringVar(&plugin.VarStringAPI, "api", "", "The api file")
|
newCmdFlags.StringVar(&new.VarStringBranch, "branch")
|
||||||
pluginCmd.Flags().StringVar(&plugin.VarStringStyle, "style", "",
|
newCmdFlags.StringVarWithDefaultValue(&new.VarStringStyle, "style", config.DefaultFormat)
|
||||||
"The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
|
|
||||||
|
|
||||||
tsCmd.Flags().StringVar(&tsgen.VarStringDir, "dir", "", "The target dir")
|
pluginCmdFlags.StringVarP(&plugin.VarStringPlugin, "plugin", "p")
|
||||||
tsCmd.Flags().StringVar(&tsgen.VarStringAPI, "api", "", "The api file")
|
pluginCmdFlags.StringVar(&plugin.VarStringDir, "dir")
|
||||||
tsCmd.Flags().StringVar(&tsgen.VarStringCaller, "caller", "", "The web api caller")
|
pluginCmdFlags.StringVar(&plugin.VarStringAPI, "api")
|
||||||
tsCmd.Flags().BoolVar(&tsgen.VarBoolUnWrap, "unwrap", false, "Unwrap the webapi caller for import")
|
pluginCmdFlags.StringVar(&plugin.VarStringStyle, "style")
|
||||||
|
|
||||||
validateCmd.Flags().StringVar(&validate.VarStringAPI, "api", "", "Validate target api file")
|
tsCmdFlags.StringVar(&tsgen.VarStringDir, "dir")
|
||||||
|
tsCmdFlags.StringVar(&tsgen.VarStringAPI, "api")
|
||||||
|
tsCmdFlags.StringVar(&tsgen.VarStringCaller, "caller")
|
||||||
|
tsCmdFlags.BoolVar(&tsgen.VarBoolUnWrap, "unwrap")
|
||||||
|
|
||||||
|
validateCmdFlags.StringVar(&validate.VarStringAPI, "api")
|
||||||
|
|
||||||
// Add sub-commands
|
// Add sub-commands
|
||||||
Cmd.AddCommand(dartCmd)
|
Cmd.AddCommand(dartCmd, docCmd, formatCmd, goCmd, javaCmd, ktCmd, newCmd, pluginCmd, tsCmd, validateCmd)
|
||||||
Cmd.AddCommand(docCmd)
|
|
||||||
Cmd.AddCommand(formatCmd)
|
|
||||||
Cmd.AddCommand(goCmd)
|
|
||||||
Cmd.AddCommand(javaCmd)
|
|
||||||
Cmd.AddCommand(ktCmd)
|
|
||||||
Cmd.AddCommand(newCmd)
|
|
||||||
Cmd.AddCommand(pluginCmd)
|
|
||||||
Cmd.AddCommand(tsCmd)
|
|
||||||
Cmd.AddCommand(validateCmd)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ var (
|
|||||||
VarStringLegacy bool
|
VarStringLegacy bool
|
||||||
// VarStringHostname defines the hostname.
|
// VarStringHostname defines the hostname.
|
||||||
VarStringHostname string
|
VarStringHostname string
|
||||||
|
// VarStringSchema defines the scheme.
|
||||||
|
VarStringScheme string
|
||||||
)
|
)
|
||||||
|
|
||||||
// DartCommand create dart network request code
|
// DartCommand create dart network request code
|
||||||
@@ -27,6 +29,7 @@ func DartCommand(_ *cobra.Command, _ []string) error {
|
|||||||
dir := VarStringDir
|
dir := VarStringDir
|
||||||
isLegacy := VarStringLegacy
|
isLegacy := VarStringLegacy
|
||||||
hostname := VarStringHostname
|
hostname := VarStringHostname
|
||||||
|
scheme := VarStringScheme
|
||||||
if len(apiFile) == 0 {
|
if len(apiFile) == 0 {
|
||||||
return errors.New("missing -api")
|
return errors.New("missing -api")
|
||||||
}
|
}
|
||||||
@@ -37,6 +40,10 @@ func DartCommand(_ *cobra.Command, _ []string) error {
|
|||||||
fmt.Println("you could use '-hostname' flag to specify your server hostname")
|
fmt.Println("you could use '-hostname' flag to specify your server hostname")
|
||||||
hostname = "go-zero.dev"
|
hostname = "go-zero.dev"
|
||||||
}
|
}
|
||||||
|
if len(scheme) == 0 {
|
||||||
|
fmt.Println("you could use '-scheme' flag to specify your server scheme")
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
api, err := parser.Parse(apiFile)
|
api, err := parser.Parse(apiFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,7 +61,7 @@ func DartCommand(_ *cobra.Command, _ []string) error {
|
|||||||
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
|
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
|
||||||
logx.Must(genData(dir+"data/", api, isLegacy))
|
logx.Must(genData(dir+"data/", api, isLegacy))
|
||||||
logx.Must(genApi(dir+"api/", api, isLegacy))
|
logx.Must(genApi(dir+"api/", api, isLegacy))
|
||||||
logx.Must(genVars(dir+"vars/", isLegacy, hostname))
|
logx.Must(genVars(dir+"vars/", isLegacy, scheme, hostname))
|
||||||
if err := formatDir(dir); err != nil {
|
if err := formatDir(dir); err != nil {
|
||||||
logx.Errorf("failed to format, %v", err)
|
logx.Errorf("failed to format, %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const dataTemplate = `// --{{with .Info}}{{.Title}}{{end}}--
|
|||||||
class {{.Name}}{
|
class {{.Name}}{
|
||||||
{{range .Members}}
|
{{range .Members}}
|
||||||
/// {{.Comment}}
|
/// {{.Comment}}
|
||||||
final {{.Type.Name}} {{lowCamelCase .Name}};
|
final {{if isNumberType .Type.Name}}num{{else}}{{.Type.Name}}{{end}} {{lowCamelCase .Name}};
|
||||||
{{end}}
|
{{end}}
|
||||||
{{.Name}}({ {{range .Members}}
|
{{.Name}}({ {{range .Members}}
|
||||||
this.{{lowCamelCase .Name}},{{end}}
|
this.{{lowCamelCase .Name}},{{end}}
|
||||||
@@ -37,7 +37,7 @@ const dataTemplateV2 = `// --{{with .Info}}{{.Title}}{{end}}--
|
|||||||
class {{.Name}} {
|
class {{.Name}} {
|
||||||
{{range .Members}}
|
{{range .Members}}
|
||||||
{{if .Comment}}{{.Comment}}{{end}}
|
{{if .Comment}}{{.Comment}}{{end}}
|
||||||
final {{.Type.Name}} {{lowCamelCase .Name}};
|
final {{if isNumberType .Type.Name}}num{{else}}{{.Type.Name}}{{end}} {{lowCamelCase .Name}};
|
||||||
{{end}}{{.Name}}({{if .Members}}{
|
{{end}}{{.Name}}({{if .Members}}{
|
||||||
{{range .Members}} required this.{{lowCamelCase .Name}},
|
{{range .Members}} required this.{{lowCamelCase .Name}},
|
||||||
{{end}}}{{end}});
|
{{end}}}{{end}});
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ Future<Tokens?> getTokens() async {
|
|||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func genVars(dir string, isLegacy bool, hostname string) error {
|
func genVars(dir string, isLegacy bool, scheme string, hostname string) error {
|
||||||
err := os.MkdirAll(dir, 0o755)
|
err := os.MkdirAll(dir, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fileExists(dir + "vars.dart") {
|
if !fileExists(dir + "vars.dart") {
|
||||||
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s';`, hostname)), 0o644)
|
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ func isAtomicType(s string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNumberType(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case "int", "double":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isListType(s string) bool {
|
func isListType(s string) bool {
|
||||||
return strings.HasPrefix(s, "List<")
|
return strings.HasPrefix(s, "List<")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var funcMap = template.FuncMap{
|
|||||||
"getBaseName": getBaseName,
|
"getBaseName": getBaseName,
|
||||||
"getPropertyFromMember": getPropertyFromMember,
|
"getPropertyFromMember": getPropertyFromMember,
|
||||||
"isDirectType": isDirectType,
|
"isDirectType": isDirectType,
|
||||||
|
"isNumberType": isNumberType,
|
||||||
"isClassListType": isClassListType,
|
"isClassListType": isClassListType,
|
||||||
"getCoreType": getCoreType,
|
"getCoreType": getCoreType,
|
||||||
"lowCamelCase": lowCamelCase,
|
"lowCamelCase": lowCamelCase,
|
||||||
@@ -56,12 +57,21 @@ Future _apiRequest(String method, String path, dynamic data,
|
|||||||
var client = HttpClient();
|
var client = HttpClient();
|
||||||
HttpClientRequest r;
|
HttpClientRequest r;
|
||||||
if (method == 'POST') {
|
if (method == 'POST') {
|
||||||
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
|
r = await client.postUrl(Uri.parse(serverHost + path));
|
||||||
} else {
|
} else {
|
||||||
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
|
r = await client.getUrl(Uri.parse(serverHost + path));
|
||||||
|
}
|
||||||
|
|
||||||
|
var strData = '';
|
||||||
|
if (data != null) {
|
||||||
|
strData = jsonEncode(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == 'POST') {
|
||||||
|
r.headers.set('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
r.headers.set('Content-Length', utf8.encode(strData).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
r.headers.set('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
if (tokens != null) {
|
if (tokens != null) {
|
||||||
r.headers.set('Authorization', tokens.accessToken);
|
r.headers.set('Authorization', tokens.accessToken);
|
||||||
}
|
}
|
||||||
@@ -70,11 +80,9 @@ Future _apiRequest(String method, String path, dynamic data,
|
|||||||
r.headers.set(k, v);
|
r.headers.set(k, v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var strData = '';
|
|
||||||
if (data != null) {
|
|
||||||
strData = jsonEncode(data);
|
|
||||||
}
|
|
||||||
r.write(strData);
|
r.write(strData);
|
||||||
|
|
||||||
var rp = await r.close();
|
var rp = await r.close();
|
||||||
var body = await rp.transform(utf8.decoder).join();
|
var body = await rp.transform(utf8.decoder).join();
|
||||||
print('${rp.statusCode} - $path');
|
print('${rp.statusCode} - $path');
|
||||||
@@ -147,12 +155,19 @@ Future _apiRequest(String method, String path, dynamic data,
|
|||||||
var client = HttpClient();
|
var client = HttpClient();
|
||||||
HttpClientRequest r;
|
HttpClientRequest r;
|
||||||
if (method == 'POST') {
|
if (method == 'POST') {
|
||||||
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
|
r = await client.postUrl(Uri.parse(serverHost + path));
|
||||||
} else {
|
} else {
|
||||||
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
|
r = await client.getUrl(Uri.parse(serverHost + path));
|
||||||
}
|
}
|
||||||
|
|
||||||
r.headers.set('Content-Type', 'application/json; charset=utf-8');
|
var strData = '';
|
||||||
|
if (data != null) {
|
||||||
|
strData = jsonEncode(data);
|
||||||
|
}
|
||||||
|
if (method == 'POST') {
|
||||||
|
r.headers.set('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
r.headers.set('Content-Length', utf8.encode(strData).length);
|
||||||
|
}
|
||||||
if (tokens != null) {
|
if (tokens != null) {
|
||||||
r.headers.set('Authorization', tokens.accessToken);
|
r.headers.set('Authorization', tokens.accessToken);
|
||||||
}
|
}
|
||||||
@@ -161,10 +176,7 @@ Future _apiRequest(String method, String path, dynamic data,
|
|||||||
r.headers.set(k, v);
|
r.headers.set(k, v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var strData = '';
|
|
||||||
if (data != null) {
|
|
||||||
strData = jsonEncode(data);
|
|
||||||
}
|
|
||||||
r.write(strData);
|
r.write(strData);
|
||||||
var rp = await r.close();
|
var rp = await r.close();
|
||||||
var body = await rp.transform(utf8.decoder).join();
|
var body = await rp.transform(utf8.decoder).join();
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/errorx"
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
"github.com/zeromicro/go-zero/tools/goctl/api/parser"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/api/util"
|
"github.com/zeromicro/go-zero/tools/goctl/api/util"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
|
||||||
|
apiF "github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/format"
|
||||||
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,6 +92,10 @@ func apiFormatReader(reader io.Reader, filename string, skipCheckDeclare bool) e
|
|||||||
|
|
||||||
// ApiFormatByPath format api from file path
|
// ApiFormatByPath format api from file path
|
||||||
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
|
func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
|
||||||
|
if env.UseExperimental() {
|
||||||
|
return apiF.File(apiFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(apiFilePath)
|
data, err := os.ReadFile(apiFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func sweep() error {
|
|||||||
|
|
||||||
tm := time.Unix(seconds, 0)
|
tm := time.Unix(seconds, 0)
|
||||||
if tm.Before(keepTime) {
|
if tm.Before(keepTime) {
|
||||||
if err := os.Remove(fpath); err != nil {
|
if err := os.RemoveAll(fpath); err != nil {
|
||||||
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
|
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package {{with .Info}}{{.Desc}}{{end}}
|
package {{.Pkg}}
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
|
||||||
|
|||||||
@@ -82,5 +82,9 @@ func genApi(dir, pkg string, api *spec.ApiSpec) error {
|
|||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
return t.Execute(file, api)
|
type data struct {
|
||||||
|
*spec.ApiSpec
|
||||||
|
Pkg string
|
||||||
|
}
|
||||||
|
return t.Execute(file, data{api, pkg})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user