Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6749c5b94a | ||
|
|
e66cca3710 | ||
|
|
f90c0aa98e | ||
|
|
f00b5416a3 | ||
|
|
f49694d6b6 | ||
|
|
d809bf2dca | ||
|
|
44ae5463bc | ||
|
|
40dbd722d7 | ||
|
|
709574133b | ||
|
|
cb1c593108 | ||
|
|
6ecf575c00 | ||
|
|
b8fcdd5460 | ||
|
|
ce42281568 | ||
|
|
40230d79e7 | ||
|
|
ba7851795b | ||
|
|
096fe3bc47 | ||
|
|
e37858295a | ||
|
|
5a4afb1518 | ||
|
|
63f1f39c40 | ||
|
|
481895d1e4 | ||
|
|
9e9ce3bf48 | ||
|
|
0ce654968d | ||
|
|
2703493541 | ||
|
|
d4240cd4b0 | ||
|
|
a22bcc84a3 | ||
|
|
93f430a449 | ||
|
|
d1b303fe7e | ||
|
|
dbca20e3df | ||
|
|
b3ead4d76c | ||
|
|
33a9db85c8 | ||
|
|
e7d46aa6e2 | ||
|
|
b282304054 | ||
|
|
0a36031d48 | ||
|
|
e5d7c3ab04 | ||
|
|
12c08bfd39 |
@@ -1,4 +1,4 @@
|
||||
ignore:
|
||||
- "doc"
|
||||
- "example"
|
||||
- "tools"
|
||||
- "tools"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"MD010": false,
|
||||
"MD013": false,
|
||||
"MD033": false,
|
||||
"MD034": false
|
||||
}
|
||||
@@ -13,11 +13,6 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const (
|
||||
StateClosed State = iota
|
||||
StateOpen
|
||||
)
|
||||
|
||||
const (
|
||||
numHistoryReasons = 5
|
||||
timeFormat = "15:04:05"
|
||||
@@ -27,7 +22,6 @@ const (
|
||||
var ErrServiceUnavailable = errors.New("circuit breaker is open")
|
||||
|
||||
type (
|
||||
State = int32
|
||||
Acceptable func(err error) bool
|
||||
|
||||
Breaker interface {
|
||||
|
||||
@@ -41,10 +41,13 @@ func GetBreaker(name string) Breaker {
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
b, ok = breakers[name]
|
||||
if !ok {
|
||||
b = NewBreaker(WithName(name))
|
||||
breakers[name] = b
|
||||
}
|
||||
lock.Unlock()
|
||||
|
||||
b = NewBreaker()
|
||||
breakers[name] = b
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -55,20 +58,5 @@ func NoBreakFor(name string) {
|
||||
}
|
||||
|
||||
func do(name string, execute func(b Breaker) error) error {
|
||||
lock.RLock()
|
||||
b, ok := breakers[name]
|
||||
lock.RUnlock()
|
||||
if ok {
|
||||
return execute(b)
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
b, ok = breakers[name]
|
||||
if !ok {
|
||||
b = NewBreaker(WithName(name))
|
||||
breakers[name] = b
|
||||
}
|
||||
lock.Unlock()
|
||||
|
||||
return execute(b)
|
||||
return execute(GetBreaker(name))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package breaker
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
@@ -21,7 +20,6 @@ const (
|
||||
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
|
||||
type googleBreaker struct {
|
||||
k float64
|
||||
state int32
|
||||
stat *collection.RollingWindow
|
||||
proba *mathx.Proba
|
||||
}
|
||||
@@ -32,7 +30,6 @@ func newGoogleBreaker() *googleBreaker {
|
||||
return &googleBreaker{
|
||||
stat: st,
|
||||
k: k,
|
||||
state: StateClosed,
|
||||
proba: mathx.NewProba(),
|
||||
}
|
||||
}
|
||||
@@ -43,15 +40,9 @@ func (b *googleBreaker) accept() error {
|
||||
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
|
||||
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
|
||||
if dropRatio <= 0 {
|
||||
if atomic.LoadInt32(&b.state) == StateOpen {
|
||||
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&b.state) == StateClosed {
|
||||
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
|
||||
}
|
||||
if b.proba.TrueOnProba(dropRatio) {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ func getGoogleBreaker() *googleBreaker {
|
||||
return &googleBreaker{
|
||||
stat: st,
|
||||
k: 5,
|
||||
state: StateClosed,
|
||||
proba: mathx.NewProba(),
|
||||
}
|
||||
}
|
||||
|
||||
86
core/cmdline/input_test.go
Normal file
86
core/cmdline/input_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package cmdline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
)
|
||||
|
||||
func TestEnterToContinue(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
assert.Nil(t, err)
|
||||
ow := os.Stdout
|
||||
os.Stdout = w
|
||||
or := os.Stdin
|
||||
os.Stdin = r
|
||||
defer func() {
|
||||
os.Stdin = or
|
||||
os.Stdout = ow
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
fmt.Println()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
EnterToContinue()
|
||||
}()
|
||||
|
||||
wait := make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(wait)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timeout")
|
||||
case <-wait:
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadLine(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
assert.Nil(t, err)
|
||||
ow := os.Stdout
|
||||
os.Stdout = w
|
||||
or := os.Stdin
|
||||
os.Stdin = r
|
||||
defer func() {
|
||||
os.Stdin = or
|
||||
os.Stdout = ow
|
||||
}()
|
||||
|
||||
const message = "hello"
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
fmt.Println(message)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
input := ReadLine("")
|
||||
assert.Equal(t, message, input)
|
||||
}()
|
||||
|
||||
wait := make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(wait)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timeout")
|
||||
case <-wait:
|
||||
}
|
||||
}
|
||||
@@ -71,3 +71,12 @@ func TestDiffieHellmanMiddleManAttack(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, string(src), string(decryptedSrc))
|
||||
}
|
||||
|
||||
func TestKeyBytes(t *testing.T) {
|
||||
var empty DhKey
|
||||
assert.Equal(t, 0, len(empty.Bytes()))
|
||||
|
||||
key, err := GenerateKey()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(key.Bytes()) > 0)
|
||||
}
|
||||
|
||||
19
core/codec/hmac_test.go
Normal file
19
core/codec/hmac_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHmac(t *testing.T) {
|
||||
ret := Hmac([]byte("foo"), "bar")
|
||||
assert.Equal(t, "f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||
fmt.Sprintf("%x", ret))
|
||||
}
|
||||
|
||||
func TestHmacBase64(t *testing.T) {
|
||||
ret := HmacBase64([]byte("foo"), "bar")
|
||||
assert.Equal(t, "+TILrwJJFp5zhQzWFW3tAQbiu2rYyrAbe7vr5tEGUxc=", ret)
|
||||
}
|
||||
59
core/codec/rsa_test.go
Normal file
59
core/codec/rsa_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
priKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQC4TJk3onpqb2RYE3wwt23J9SHLFstHGSkUYFLe+nl1dEKHbD+/
|
||||
Zt95L757J3xGTrwoTc7KCTxbrgn+stn0w52BNjj/kIE2ko4lbh/v8Fl14AyVR9ms
|
||||
fKtKOnhe5FCT72mdtApr+qvzcC3q9hfXwkyQU32pv7q5UimZ205iKSBmgQIDAQAB
|
||||
AoGAM5mWqGIAXj5z3MkP01/4CDxuyrrGDVD5FHBno3CDgyQa4Gmpa4B0/ywj671B
|
||||
aTnwKmSmiiCN2qleuQYASixes2zY5fgTzt+7KNkl9JHsy7i606eH2eCKzsUa/s6u
|
||||
WD8V3w/hGCQ9zYI18ihwyXlGHIgcRz/eeRh+nWcWVJzGOPUCQQD5nr6It/1yHb1p
|
||||
C6l4fC4xXF19l4KxJjGu1xv/sOpSx0pOqBDEX3Mh//FU954392rUWDXV1/I65BPt
|
||||
TLphdsu3AkEAvQJ2Qay/lffFj9FaUrvXuftJZ/Ypn0FpaSiUh3Ak3obBT6UvSZS0
|
||||
bcYdCJCNHDtBOsWHnIN1x+BcWAPrdU7PhwJBAIQ0dUlH2S3VXnoCOTGc44I1Hzbj
|
||||
Rc65IdsuBqA3fQN2lX5vOOIog3vgaFrOArg1jBkG1wx5IMvb/EnUN2pjVqUCQCza
|
||||
KLXtCInOAlPemlCHwumfeAvznmzsWNdbieOZ+SXVVIpR6KbNYwOpv7oIk3Pfm9sW
|
||||
hNffWlPUKhW42Gc+DIECQQDmk20YgBXwXWRM5DRPbhisIV088N5Z58K9DtFWkZsd
|
||||
OBDT3dFcgZONtlmR1MqZO0pTh30lA4qovYj3Bx7A8i36
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
pubKey = `-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4TJk3onpqb2RYE3wwt23J9SHL
|
||||
FstHGSkUYFLe+nl1dEKHbD+/Zt95L757J3xGTrwoTc7KCTxbrgn+stn0w52BNjj/
|
||||
kIE2ko4lbh/v8Fl14AyVR9msfKtKOnhe5FCT72mdtApr+qvzcC3q9hfXwkyQU32p
|
||||
v7q5UimZ205iKSBmgQIDAQAB
|
||||
-----END PUBLIC KEY-----`
|
||||
testBody = `this is the content`
|
||||
encryptedBody = `49e7bc15640e5d927fd3f129b749536d0755baf03a0f35fc914ff1b7b8ce659e5fe3a598442eb908c5995e28bacd3d76e4420bb05b6bfc177040f66c6976f680f7123505d626ab96a9db1151f45c93bc0262db9087b9fb6801715f76f902e644a20029262858f05b0d10540842204346ac1d6d8f29cc5d47dab79af75d922ef2`
|
||||
)
|
||||
|
||||
func TestCryption(t *testing.T) {
|
||||
enc, err := NewRsaEncrypter([]byte(pubKey))
|
||||
assert.Nil(t, err)
|
||||
ret, err := enc.Encrypt([]byte(testBody))
|
||||
assert.Nil(t, err)
|
||||
|
||||
file, err := fs.TempFilenameWithText(priKey)
|
||||
assert.Nil(t, err)
|
||||
dec, err := NewRsaDecrypter(file)
|
||||
assert.Nil(t, err)
|
||||
actual, err := dec.Decrypt(ret)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testBody, string(actual))
|
||||
|
||||
actual, err = dec.DecryptBase64(base64.StdEncoding.EncodeToString(ret))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testBody, string(actual))
|
||||
}
|
||||
|
||||
func TestBadPubKey(t *testing.T) {
|
||||
_, err := NewRsaEncrypter([]byte("foo"))
|
||||
assert.Equal(t, ErrPublicKey, err)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
stringType
|
||||
)
|
||||
|
||||
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
|
||||
type Set struct {
|
||||
data map[interface{}]lang.PlaceholderType
|
||||
tp int
|
||||
@@ -182,10 +183,7 @@ func (s *Set) add(i interface{}) {
|
||||
}
|
||||
|
||||
func (s *Set) setType(i interface{}) {
|
||||
if s.tp != untyped {
|
||||
return
|
||||
}
|
||||
|
||||
// s.tp can only be untyped here
|
||||
switch i.(type) {
|
||||
case int:
|
||||
s.tp = intType
|
||||
|
||||
@@ -5,8 +5,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logx.Disable()
|
||||
}
|
||||
|
||||
func BenchmarkRawSet(b *testing.B) {
|
||||
m := make(map[interface{}]struct{})
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -147,3 +152,51 @@ func TestCount(t *testing.T) {
|
||||
// then
|
||||
assert.Equal(t, set.Count(), 3)
|
||||
}
|
||||
|
||||
func TestKeysIntMismatch(t *testing.T) {
|
||||
set := NewSet()
|
||||
set.add(int64(1))
|
||||
set.add(2)
|
||||
vals := set.KeysInt()
|
||||
assert.EqualValues(t, []int{2}, vals)
|
||||
}
|
||||
|
||||
func TestKeysInt64Mismatch(t *testing.T) {
|
||||
set := NewSet()
|
||||
set.add(1)
|
||||
set.add(int64(2))
|
||||
vals := set.KeysInt64()
|
||||
assert.EqualValues(t, []int64{2}, vals)
|
||||
}
|
||||
|
||||
func TestKeysUintMismatch(t *testing.T) {
|
||||
set := NewSet()
|
||||
set.add(1)
|
||||
set.add(uint(2))
|
||||
vals := set.KeysUint()
|
||||
assert.EqualValues(t, []uint{2}, vals)
|
||||
}
|
||||
|
||||
func TestKeysUint64Mismatch(t *testing.T) {
|
||||
set := NewSet()
|
||||
set.add(1)
|
||||
set.add(uint64(2))
|
||||
vals := set.KeysUint64()
|
||||
assert.EqualValues(t, []uint64{2}, vals)
|
||||
}
|
||||
|
||||
func TestKeysStrMismatch(t *testing.T) {
|
||||
set := NewSet()
|
||||
set.add(1)
|
||||
set.add("2")
|
||||
vals := set.KeysStr()
|
||||
assert.EqualValues(t, []string{"2"}, vals)
|
||||
}
|
||||
|
||||
func TestSetType(t *testing.T) {
|
||||
set := NewUnmanagedSet()
|
||||
set.add(1)
|
||||
set.add("2")
|
||||
vals := set.Keys()
|
||||
assert.ElementsMatch(t, []interface{}{1, "2"}, vals)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,7 @@ func TestConfigJson(t *testing.T) {
|
||||
A string `json:"a"`
|
||||
B int `json:"b"`
|
||||
}
|
||||
err = LoadConfig(tmpfile, &val)
|
||||
assert.Nil(t, err)
|
||||
MustLoad(tmpfile, &val)
|
||||
assert.Equal(t, "foo", val.A)
|
||||
assert.Equal(t, 1, val.B)
|
||||
})
|
||||
|
||||
@@ -30,12 +30,12 @@ type mapBasedProperties struct {
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// Loads the properties into a properties configuration instance. May return the
|
||||
// configuration itself along with an error that indicates if there was a problem loading the configuration.
|
||||
// Loads the properties into a properties configuration instance.
|
||||
// Returns an error that indicates if there was a problem loading the configuration.
|
||||
func LoadProperties(filename string) (Properties, error) {
|
||||
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := make(map[string]string)
|
||||
|
||||
@@ -41,3 +41,8 @@ func TestSetInt(t *testing.T) {
|
||||
props.SetInt(key, value)
|
||||
assert.Equal(t, value, props.GetInt(key))
|
||||
}
|
||||
|
||||
func TestLoadBadFile(t *testing.T) {
|
||||
_, err := LoadProperties("nosuchfile")
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
@@ -85,6 +85,46 @@ func TestStructedLogSlow(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSlowf(t *testing.T) {
|
||||
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||
slowLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
Slowf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogStat(t *testing.T) {
|
||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||
statLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
Stat(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogStatf(t *testing.T) {
|
||||
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||
statLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
Statf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSevere(t *testing.T) {
|
||||
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||
severeLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
Severe(v...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogSeveref(t *testing.T) {
|
||||
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||
severeLog = writer
|
||||
}, func(v ...interface{}) {
|
||||
Severef(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogWithDuration(t *testing.T) {
|
||||
const message = "hello there"
|
||||
writer := new(mockWriter)
|
||||
@@ -135,6 +175,15 @@ func TestMustNil(t *testing.T) {
|
||||
Must(nil)
|
||||
}
|
||||
|
||||
func TestDisable(t *testing.T) {
|
||||
Disable()
|
||||
WithKeepDays(1)
|
||||
WithGzip()
|
||||
assert.Nil(t, Close())
|
||||
writeConsole = false
|
||||
assert.Nil(t, Close())
|
||||
}
|
||||
|
||||
func BenchmarkCopyByteSliceAppend(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var buf []byte
|
||||
@@ -232,7 +281,7 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, level, entry.Level)
|
||||
assert.Equal(t, message, entry.Content)
|
||||
assert.True(t, strings.Contains(entry.Content, message))
|
||||
}
|
||||
|
||||
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
||||
|
||||
@@ -192,14 +192,16 @@ func (l *RotateLogger) init() error {
|
||||
}
|
||||
|
||||
func (l *RotateLogger) maybeCompressFile(file string) {
|
||||
if l.compress {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ErrorStack(r)
|
||||
}
|
||||
}()
|
||||
compressLogFile(file)
|
||||
if !l.compress {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ErrorStack(r)
|
||||
}
|
||||
}()
|
||||
compressLogFile(file)
|
||||
}
|
||||
|
||||
func (l *RotateLogger) maybeDeleteOutdatedFiles() {
|
||||
|
||||
119
core/logx/rotatelogger_test.go
Normal file
119
core/logx/rotatelogger_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/fs"
|
||||
)
|
||||
|
||||
func TestDailyRotateRuleMarkRotated(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
|
||||
assert.True(t, rule.ShallRotate())
|
||||
}
|
||||
|
||||
func TestRotateLoggerClose(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
}
|
||||
|
||||
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(logger.getBackupFilename()) > 0)
|
||||
logger.backup = ""
|
||||
assert.True(t, len(logger.getBackupFilename()) > 0)
|
||||
}
|
||||
|
||||
func TestRotateLoggerMayCompressFile(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
logger.maybeCompressFile(filename)
|
||||
_, err = os.Stat(filename)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), true)
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
}
|
||||
logger.maybeCompressFile(filename)
|
||||
_, err = os.Stat(filename)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestRotateLoggerRotate(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), true)
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(logger.getBackupFilename())
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
}
|
||||
err = logger.rotate()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRotateLoggerWrite(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
rule := new(DailyRotateRule)
|
||||
logger, err := NewLogger(filename, rule, true)
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer func() {
|
||||
os.Remove(filename)
|
||||
os.Remove(logger.getBackupFilename())
|
||||
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
|
||||
}()
|
||||
}
|
||||
logger.write([]byte(`foo`))
|
||||
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
|
||||
logger.write([]byte(`bar`))
|
||||
}
|
||||
@@ -33,10 +33,10 @@ func captureOutput(f func()) string {
|
||||
writer := new(mockWriter)
|
||||
infoLog = writer
|
||||
|
||||
prevLevel := logLevel
|
||||
logLevel = InfoLevel
|
||||
prevLevel := atomic.LoadUint32(&logLevel)
|
||||
SetLevel(InfoLevel)
|
||||
f()
|
||||
logLevel = prevLevel
|
||||
SetLevel(prevLevel)
|
||||
|
||||
return writer.builder.String()
|
||||
}
|
||||
|
||||
31
core/mapping/fieldoptions_test.go
Normal file
31
core/mapping/fieldoptions_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Bar struct {
|
||||
Val string `json:"val"`
|
||||
}
|
||||
|
||||
func TestFieldOptionOptionalDep(t *testing.T) {
|
||||
var bar Bar
|
||||
rt := reflect.TypeOf(bar)
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
field := rt.Field(i)
|
||||
val, opt, err := parseKeyAndOptions(jsonTagKey, field)
|
||||
assert.Equal(t, "val", val)
|
||||
assert.Nil(t, opt)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// check nil working
|
||||
var o *fieldOptions
|
||||
check := func(o *fieldOptions) {
|
||||
assert.Equal(t, 0, len(o.optionalDep()))
|
||||
}
|
||||
check(o)
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
var (
|
||||
errTypeMismatch = errors.New("type mismatch")
|
||||
errValueNotSettable = errors.New("value is not settable")
|
||||
errValueNotStruct = errors.New("value type is not struct")
|
||||
keyUnmarshaler = NewUnmarshaler(defaultKeyName)
|
||||
cacheKeys atomic.Value
|
||||
cacheKeysLock sync.Mutex
|
||||
@@ -80,6 +81,10 @@ func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName st
|
||||
}
|
||||
|
||||
rte := reflect.TypeOf(v).Elem()
|
||||
if rte.Kind() != reflect.Struct {
|
||||
return errValueNotStruct
|
||||
}
|
||||
|
||||
rve := rv.Elem()
|
||||
numFields := rte.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
|
||||
@@ -14,6 +14,13 @@ import (
|
||||
// so we only can test to 62 bits.
|
||||
const maxUintBitsToTest = 62
|
||||
|
||||
func TestUnmarshalWithFullNameNotStruct(t *testing.T) {
|
||||
var s map[string]interface{}
|
||||
content := []byte(`{"name":"xiaoming"}`)
|
||||
err := UnmarshalJsonBytes(content, &s)
|
||||
assert.Equal(t, errValueNotStruct, err)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithoutTagName(t *testing.T) {
|
||||
type inner struct {
|
||||
Optional bool `key:",optional"`
|
||||
@@ -2380,6 +2387,13 @@ func TestUnmarshalNestedMapSimpleTypeMatch(t *testing.T) {
|
||||
assert.Equal(t, "1", c.Anything["id"])
|
||||
}
|
||||
|
||||
func TestUnmarshalValuer(t *testing.T) {
|
||||
unmarshaler := NewUnmarshaler(jsonTagKey)
|
||||
var foo string
|
||||
err := unmarshaler.UnmarshalValuer(nil, foo)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalString(b *testing.B) {
|
||||
type inner struct {
|
||||
Value string `key:"value"`
|
||||
|
||||
16
core/proc/goroutines_test.go
Normal file
16
core/proc/goroutines_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDumpGoroutines(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
dumpGoroutines()
|
||||
assert.True(t, strings.Contains(buf.String(), ".dump"))
|
||||
}
|
||||
21
core/proc/profile_test.go
Normal file
21
core/proc/profile_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
log.SetOutput(&buf)
|
||||
profiler := StartProfile()
|
||||
// start again should not work
|
||||
assert.NotNil(t, StartProfile())
|
||||
profiler.Stop()
|
||||
// stop twice
|
||||
profiler.Stop()
|
||||
assert.True(t, strings.Contains(buf.String(), ".pprof"))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
4
core/stores/cache/cacheconf.go
vendored
4
core/stores/cache/cacheconf.go
vendored
@@ -1,5 +1,3 @@
|
||||
package cache
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/internal"
|
||||
|
||||
type CacheConf = internal.ClusterConf
|
||||
type CacheConf = ClusterConf
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
36
core/stores/cache/cacheopt.go
vendored
36
core/stores/cache/cacheopt.go
vendored
@@ -1,21 +1,45 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
import "time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
const (
|
||||
defaultExpiry = time.Hour * 24 * 7
|
||||
defaultNotFoundExpiry = time.Minute
|
||||
)
|
||||
|
||||
type Option = internal.Option
|
||||
type (
|
||||
Options struct {
|
||||
Expiry time.Duration
|
||||
NotFoundExpiry time.Duration
|
||||
}
|
||||
|
||||
Option func(o *Options)
|
||||
)
|
||||
|
||||
func newOptions(opts ...Option) Options {
|
||||
var o Options
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
if o.Expiry <= 0 {
|
||||
o.Expiry = defaultExpiry
|
||||
}
|
||||
if o.NotFoundExpiry <= 0 {
|
||||
o.NotFoundExpiry = defaultNotFoundExpiry
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func WithExpiry(expiry time.Duration) Option {
|
||||
return func(o *internal.Options) {
|
||||
return func(o *Options) {
|
||||
o.Expiry = expiry
|
||||
}
|
||||
}
|
||||
|
||||
func WithNotFoundExpiry(expiry time.Duration) Option {
|
||||
return func(o *internal.Options) {
|
||||
return func(o *Options) {
|
||||
o.NotFoundExpiry = expiry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
56
core/stores/cache/cleaner_test.go
vendored
Normal file
56
core/stores/cache/cleaner_test.go
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNextDelay(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input time.Duration
|
||||
output time.Duration
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "second",
|
||||
input: time.Second,
|
||||
output: time.Second * 5,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "5 seconds",
|
||||
input: time.Second * 5,
|
||||
output: time.Minute,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "minute",
|
||||
input: time.Minute,
|
||||
output: time.Minute * 5,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "5 minutes",
|
||||
input: time.Minute * 5,
|
||||
output: time.Hour,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "hour",
|
||||
input: time.Hour,
|
||||
output: 0,
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
next, ok := nextDelay(test.input)
|
||||
assert.Equal(t, test.ok, ok)
|
||||
assert.Equal(t, test.output, next)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/redis"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cache
|
||||
|
||||
import "strings"
|
||||
|
||||
26
core/stores/cache/util_test.go
vendored
Normal file
26
core/stores/cache/util_test.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormatKeys(t *testing.T) {
|
||||
assert.Equal(t, "a,b", formatKeys([]string{"a", "b"}))
|
||||
}
|
||||
|
||||
func TestTotalWeights(t *testing.T) {
|
||||
val := TotalWeights([]NodeConf{
|
||||
{
|
||||
Weight: -1,
|
||||
},
|
||||
{
|
||||
Weight: 0,
|
||||
},
|
||||
{
|
||||
Weight: 1,
|
||||
},
|
||||
})
|
||||
assert.Equal(t, 1, val)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
defaultExpiry = time.Hour * 24 * 7
|
||||
defaultNotFoundExpiry = time.Minute
|
||||
)
|
||||
|
||||
type (
|
||||
Options struct {
|
||||
Expiry time.Duration
|
||||
NotFoundExpiry time.Duration
|
||||
}
|
||||
|
||||
Option func(o *Options)
|
||||
)
|
||||
|
||||
func newOptions(opts ...Option) Options {
|
||||
var o Options
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
if o.Expiry <= 0 {
|
||||
o.Expiry = defaultExpiry
|
||||
}
|
||||
if o.NotFoundExpiry <= 0 {
|
||||
o.NotFoundExpiry = defaultNotFoundExpiry
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package kv
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/internal"
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
)
|
||||
|
||||
type KvConf = internal.ClusterConf
|
||||
type KvConf = cache.ClusterConf
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/errorx"
|
||||
"github.com/tal-tech/go-zero/core/hash"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ type (
|
||||
)
|
||||
|
||||
func NewStore(c KvConf) Store {
|
||||
if len(c) == 0 || internal.TotalWeights(c) <= 0 {
|
||||
if len(c) == 0 || cache.TotalWeights(c) <= 0 {
|
||||
log.Fatal("no cache nodes")
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/alicebob/miniredis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
@@ -478,7 +478,7 @@ func runOnCluster(t *testing.T, fn func(cluster Store)) {
|
||||
s1.FlushAll()
|
||||
s2.FlushAll()
|
||||
|
||||
store := NewStore([]internal.NodeConf{
|
||||
store := NewStore([]cache.NodeConf{
|
||||
{
|
||||
RedisConf: redis.RedisConf{
|
||||
Host: s1.Addr(),
|
||||
|
||||
@@ -22,8 +22,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func MustNewModel(url, database, collection string, opts ...Option) *Model {
|
||||
model, err := NewModel(url, database, collection, opts...)
|
||||
func MustNewModel(url, collection string, opts ...Option) *Model {
|
||||
model, err := NewModel(url, collection, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -31,15 +31,16 @@ func MustNewModel(url, database, collection string, opts ...Option) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
func NewModel(url, database, collection string, opts ...Option) (*Model, error) {
|
||||
func NewModel(url, collection string, opts ...Option) (*Model, error) {
|
||||
session, err := getConcurrentSession(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Model{
|
||||
session: session,
|
||||
db: session.DB(database),
|
||||
session: session,
|
||||
// If name is empty, the database name provided in the dialed URL is used instead
|
||||
db: session.DB(""),
|
||||
collection: collection,
|
||||
opts: opts,
|
||||
}, nil
|
||||
|
||||
@@ -2,7 +2,7 @@ package mongoc
|
||||
|
||||
import (
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/mongo"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
)
|
||||
@@ -12,7 +12,7 @@ var (
|
||||
|
||||
// can't use one SharedCalls per conn, because multiple conns may share the same cache key.
|
||||
sharedCalls = syncx.NewSharedCalls()
|
||||
stats = internal.NewCacheStat("mongoc")
|
||||
stats = cache.NewCacheStat("mongoc")
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -20,11 +20,11 @@ type (
|
||||
|
||||
cachedCollection struct {
|
||||
collection mongo.Collection
|
||||
cache internal.Cache
|
||||
cache cache.Cache
|
||||
}
|
||||
)
|
||||
|
||||
func newCollection(collection mongo.Collection, c internal.Cache) *cachedCollection {
|
||||
func newCollection(collection mongo.Collection, c cache.Cache) *cachedCollection {
|
||||
return &cachedCollection{
|
||||
collection: collection,
|
||||
cache: c,
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stat"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/mongo"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
)
|
||||
@@ -33,7 +33,7 @@ func TestStat(t *testing.T) {
|
||||
}
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
@@ -56,7 +56,7 @@ func TestStatCacheFails(t *testing.T) {
|
||||
defer log.SetOutput(os.Stdout)
|
||||
|
||||
r := redis.NewRedis("localhost:59999", redis.NodeType)
|
||||
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
@@ -79,7 +79,7 @@ func TestStatDbFails(t *testing.T) {
|
||||
}
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
@@ -103,7 +103,7 @@ func TestStatFromMemory(t *testing.T) {
|
||||
}
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||
c := newCollection(dummyConn{}, cach)
|
||||
|
||||
var all sync.WaitGroup
|
||||
|
||||
@@ -5,19 +5,18 @@ import (
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/mongo"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
*mongo.Model
|
||||
cache internal.Cache
|
||||
cache cache.Cache
|
||||
generateCollection func(*mgo.Session) *cachedCollection
|
||||
}
|
||||
|
||||
func MustNewNodeModel(url, database, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
|
||||
model, err := NewNodeModel(url, database, collection, rds, opts...)
|
||||
func MustNewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
|
||||
model, err := NewNodeModel(url, collection, rds, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -25,8 +24,8 @@ func MustNewNodeModel(url, database, collection string, rds *redis.Redis, opts .
|
||||
return model
|
||||
}
|
||||
|
||||
func MustNewModel(url, database, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
|
||||
model, err := NewModel(url, database, collection, c, opts...)
|
||||
func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
|
||||
model, err := NewModel(url, collection, c, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -34,16 +33,16 @@ func MustNewModel(url, database, collection string, c cache.CacheConf, opts ...c
|
||||
return model
|
||||
}
|
||||
|
||||
func NewNodeModel(url, database, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
|
||||
c := internal.NewCacheNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return createModel(url, database, collection, c, func(collection mongo.Collection) *cachedCollection {
|
||||
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.NewCacheNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return createModel(url, collection, c, func(collection mongo.Collection) *cachedCollection {
|
||||
return newCollection(collection, c)
|
||||
})
|
||||
}
|
||||
|
||||
func NewModel(url, database, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
|
||||
c := internal.NewCache(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return createModel(url, database, collection, c, func(collection mongo.Collection) *cachedCollection {
|
||||
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.NewCache(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return createModel(url, collection, c, func(collection mongo.Collection) *cachedCollection {
|
||||
return newCollection(collection, c)
|
||||
})
|
||||
}
|
||||
@@ -224,9 +223,9 @@ func (mm *Model) pipe(fn func(c *cachedCollection) mongo.Pipe) (mongo.Pipe, erro
|
||||
return fn(mm.GetCollection(session)), nil
|
||||
}
|
||||
|
||||
func createModel(url, database, collection string, c internal.Cache,
|
||||
func createModel(url, collection string, c cache.Cache,
|
||||
create func(mongo.Collection) *cachedCollection) (*Model, error) {
|
||||
model, err := mongo.NewModel(url, database, collection)
|
||||
model, err := mongo.NewModel(url, collection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
110
core/stores/redis/conf_test.go
Normal file
110
core/stores/redis/conf_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
func TestRedisConf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
RedisConf
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "missing host",
|
||||
RedisConf: RedisConf{
|
||||
Host: "",
|
||||
Type: NodeType,
|
||||
Pass: "",
|
||||
},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "missing type",
|
||||
RedisConf: RedisConf{
|
||||
Host: "localhost:6379",
|
||||
Type: "",
|
||||
Pass: "",
|
||||
},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
RedisConf: RedisConf{
|
||||
Host: "localhost:6379",
|
||||
Type: NodeType,
|
||||
Pass: "",
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||
if test.ok {
|
||||
assert.Nil(t, test.RedisConf.Validate())
|
||||
assert.NotNil(t, test.RedisConf.NewRedis())
|
||||
} else {
|
||||
assert.NotNil(t, test.RedisConf.Validate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisKeyConf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
RedisKeyConf
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "missing host",
|
||||
RedisKeyConf: RedisKeyConf{
|
||||
RedisConf: RedisConf{
|
||||
Host: "",
|
||||
Type: NodeType,
|
||||
Pass: "",
|
||||
},
|
||||
Key: "foo",
|
||||
},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
RedisKeyConf: RedisKeyConf{
|
||||
RedisConf: RedisConf{
|
||||
Host: "localhost:6379",
|
||||
Type: NodeType,
|
||||
Pass: "",
|
||||
},
|
||||
Key: "",
|
||||
},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
RedisKeyConf: RedisKeyConf{
|
||||
RedisConf: RedisConf{
|
||||
Host: "localhost:6379",
|
||||
Type: NodeType,
|
||||
Pass: "",
|
||||
},
|
||||
Key: "foo",
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.ok {
|
||||
assert.Nil(t, test.RedisKeyConf.Validate())
|
||||
} else {
|
||||
assert.NotNil(t, test.RedisKeyConf.Validate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis"
|
||||
red "github.com/go-redis/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRedis_Exists(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Exists("a")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Exists("a")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
@@ -24,7 +27,9 @@ func TestRedis_Exists(t *testing.T) {
|
||||
|
||||
func TestRedis_Eval(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
||||
_, err := NewRedis(client.Addr, "").Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
||||
assert.Equal(t, Nil, err)
|
||||
err = client.Set("key1", "value1")
|
||||
assert.Nil(t, err)
|
||||
@@ -40,6 +45,8 @@ func TestRedis_Hgetall(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hgetall("a")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Hgetall("a")
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, map[string]string{
|
||||
@@ -51,8 +58,11 @@ func TestRedis_Hgetall(t *testing.T) {
|
||||
|
||||
func TestRedis_Hvals(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.NotNil(t, NewRedis(client.Addr, "").Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hvals("a")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Hvals("a")
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"aaa", "bbb"}, vals)
|
||||
@@ -63,6 +73,8 @@ func TestRedis_Hsetnx(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hsetnx("a", "bb", "ccc")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Hsetnx("a", "bb", "ccc")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
@@ -79,6 +91,8 @@ func TestRedis_HdelHlen(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hlen("a")
|
||||
assert.NotNil(t, err)
|
||||
num, err := client.Hlen("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
@@ -93,6 +107,8 @@ func TestRedis_HdelHlen(t *testing.T) {
|
||||
|
||||
func TestRedis_HIncrBy(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Hincrby("key", "field", 2)
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Hincrby("key", "field", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, val)
|
||||
@@ -106,6 +122,8 @@ func TestRedis_Hkeys(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hkeys("a")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Hkeys("a")
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"aa", "bb"}, vals)
|
||||
@@ -116,6 +134,8 @@ func TestRedis_Hmget(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||
_, err := NewRedis(client.Addr, "").Hmget("a", "aa", "bb")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Hmget("a", "aa", "bb")
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"aaa", "bbb"}, vals)
|
||||
@@ -127,6 +147,7 @@ func TestRedis_Hmget(t *testing.T) {
|
||||
|
||||
func TestRedis_Hmset(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.NotNil(t, NewRedis(client.Addr, "").Hmset("a", nil))
|
||||
assert.Nil(t, client.Hmset("a", map[string]string{
|
||||
"aa": "aaa",
|
||||
"bb": "bbb",
|
||||
@@ -139,6 +160,8 @@ func TestRedis_Hmset(t *testing.T) {
|
||||
|
||||
func TestRedis_Incr(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Incr("a")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Incr("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), val)
|
||||
@@ -150,6 +173,8 @@ func TestRedis_Incr(t *testing.T) {
|
||||
|
||||
func TestRedis_IncrBy(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Incrby("a", 2)
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Incrby("a", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(2), val)
|
||||
@@ -165,26 +190,49 @@ func TestRedis_Keys(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
err = client.Set("key2", "value2")
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Keys("*")
|
||||
assert.NotNil(t, err)
|
||||
keys, err := client.Keys("*")
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_HyperLogLog(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
r := NewRedis(client.Addr, "")
|
||||
_, err := r.Pfadd("key1")
|
||||
assert.NotNil(t, err)
|
||||
_, err = r.Pfcount("*")
|
||||
assert.NotNil(t, err)
|
||||
err = r.Pfmerge("*")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_List(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Lpush("key", "value1", "value2")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Lpush("key", "value1", "value2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, val)
|
||||
_, err = NewRedis(client.Addr, "").Rpush("key", "value3", "value4")
|
||||
assert.NotNil(t, err)
|
||||
val, err = client.Rpush("key", "value3", "value4")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, val)
|
||||
_, err = NewRedis(client.Addr, "").Llen("key")
|
||||
assert.NotNil(t, err)
|
||||
val, err = client.Llen("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, val)
|
||||
vals, err := client.Lrange("key", 0, 10)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
|
||||
_, err = NewRedis(client.Addr, "").Lpop("key")
|
||||
assert.NotNil(t, err)
|
||||
v, err := client.Lpop("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "value2", v)
|
||||
@@ -194,9 +242,13 @@ func TestRedis_List(t *testing.T) {
|
||||
val, err = client.Rpush("key", "value3", "value3")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 7, val)
|
||||
_, err = NewRedis(client.Addr, "").Lrem("key", 2, "value1")
|
||||
assert.NotNil(t, err)
|
||||
n, err := client.Lrem("key", 2, "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, n)
|
||||
_, err = NewRedis(client.Addr, "").Lrange("key", 0, 10)
|
||||
assert.NotNil(t, err)
|
||||
vals, err = client.Lrange("key", 0, 10)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value2", "value3", "value4", "value3", "value3"}, vals)
|
||||
@@ -215,6 +267,8 @@ func TestRedis_Mget(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
err = client.Set("key2", "value2")
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Mget("key1", "key0", "key2", "key3")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Mget("key1", "key0", "key2", "key3")
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value1", "", "value2", ""}, vals)
|
||||
@@ -223,7 +277,9 @@ func TestRedis_Mget(t *testing.T) {
|
||||
|
||||
func TestRedis_SetBit(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := client.SetBit("key", 1, 1)
|
||||
err := NewRedis(client.Addr, "").SetBit("key", 1, 1)
|
||||
assert.NotNil(t, err)
|
||||
err = client.SetBit("key", 1, 1)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
@@ -232,6 +288,8 @@ func TestRedis_GetBit(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := client.SetBit("key", 2, 1)
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").GetBit("key", 2)
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.GetBit("key", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, val)
|
||||
@@ -240,6 +298,8 @@ func TestRedis_GetBit(t *testing.T) {
|
||||
|
||||
func TestRedis_Persist(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Persist("key")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Persist("key")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
@@ -248,11 +308,15 @@ func TestRedis_Persist(t *testing.T) {
|
||||
ok, err = client.Persist("key")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
err = NewRedis(client.Addr, "").Expire("key", 5)
|
||||
assert.NotNil(t, err)
|
||||
err = client.Expire("key", 5)
|
||||
assert.Nil(t, err)
|
||||
ok, err = client.Persist("key")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
err = NewRedis(client.Addr, "").Expireat("key", time.Now().Unix()+5)
|
||||
assert.NotNil(t, err)
|
||||
err = client.Expireat("key", time.Now().Unix()+5)
|
||||
assert.Nil(t, err)
|
||||
ok, err = client.Persist("key")
|
||||
@@ -274,6 +338,8 @@ func TestRedis_Scan(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
err = client.Set("key2", "value2")
|
||||
assert.Nil(t, err)
|
||||
_, _, err = NewRedis(client.Addr, "").Scan(0, "*", 100)
|
||||
assert.NotNil(t, err)
|
||||
keys, _, err := client.Scan(0, "*", 100)
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
||||
@@ -294,6 +360,8 @@ func TestRedis_Sscan(t *testing.T) {
|
||||
var cursor uint64 = 0
|
||||
sum := 0
|
||||
for {
|
||||
_, _, err := NewRedis(client.Addr, "").Sscan(key, cursor, "", 100)
|
||||
assert.NotNil(t, err)
|
||||
keys, next, err := client.Sscan(key, cursor, "", 100)
|
||||
assert.Nil(t, err)
|
||||
sum += len(keys)
|
||||
@@ -304,6 +372,8 @@ func TestRedis_Sscan(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, sum, 1550)
|
||||
_, err = NewRedis(client.Addr, "").Del(key)
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.Del(key)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
@@ -311,46 +381,72 @@ func TestRedis_Sscan(t *testing.T) {
|
||||
|
||||
func TestRedis_Set(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := NewRedis(client.Addr, "").Sadd("key", 1, 2, 3, 4)
|
||||
assert.NotNil(t, err)
|
||||
num, err := client.Sadd("key", 1, 2, 3, 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, num)
|
||||
_, err = NewRedis(client.Addr, "").Scard("key")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Scard("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(4), val)
|
||||
_, err = NewRedis(client.Addr, "").Sismember("key", 2)
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Sismember("key", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
_, err = NewRedis(client.Addr, "").Srem("key", 3, 4)
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Srem("key", 3, 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
_, err = NewRedis(client.Addr, "").Smembers("key")
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Smembers("key")
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"1", "2"}, vals)
|
||||
_, err = NewRedis(client.Addr, "").Srandmember("key", 1)
|
||||
assert.NotNil(t, err)
|
||||
members, err := client.Srandmember("key", 1)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, members, 1)
|
||||
assert.Contains(t, []string{"1", "2"}, members[0])
|
||||
_, err = NewRedis(client.Addr, "").Spop("key")
|
||||
assert.NotNil(t, err)
|
||||
member, err := client.Spop("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, []string{"1", "2"}, member)
|
||||
_, err = NewRedis(client.Addr, "").Smembers("key")
|
||||
assert.NotNil(t, err)
|
||||
vals, err = client.Smembers("key")
|
||||
assert.Nil(t, err)
|
||||
assert.NotContains(t, vals, member)
|
||||
_, err = NewRedis(client.Addr, "").Sadd("key1", 1, 2, 3, 4)
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Sadd("key1", 1, 2, 3, 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, num)
|
||||
num, err = client.Sadd("key2", 2, 3, 4, 5)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, num)
|
||||
_, err = NewRedis(client.Addr, "").Sunion("key1", "key2")
|
||||
assert.NotNil(t, err)
|
||||
vals, err = client.Sunion("key1", "key2")
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []string{"1", "2", "3", "4", "5"}, vals)
|
||||
_, err = NewRedis(client.Addr, "").Sunionstore("key3", "key1", "key2")
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Sunionstore("key3", "key1", "key2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, num)
|
||||
_, err = NewRedis(client.Addr, "").Sdiff("key1", "key2")
|
||||
assert.NotNil(t, err)
|
||||
vals, err = client.Sdiff("key1", "key2")
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"1"}, vals)
|
||||
_, err = NewRedis(client.Addr, "").Sdiffstore("key4", "key1", "key2")
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Sdiffstore("key4", "key1", "key2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
@@ -359,8 +455,12 @@ func TestRedis_Set(t *testing.T) {
|
||||
|
||||
func TestRedis_SetGetDel(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := client.Set("hello", "world")
|
||||
err := NewRedis(client.Addr, "").Set("hello", "world")
|
||||
assert.NotNil(t, err)
|
||||
err = client.Set("hello", "world")
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Get("hello")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Get("hello")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "world", val)
|
||||
@@ -372,8 +472,12 @@ func TestRedis_SetGetDel(t *testing.T) {
|
||||
|
||||
func TestRedis_SetExNx(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := client.Setex("hello", "world", 5)
|
||||
err := NewRedis(client.Addr, "").Setex("hello", "world", 5)
|
||||
assert.NotNil(t, err)
|
||||
err = client.Setex("hello", "world", 5)
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Setnx("hello", "newworld")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Setnx("hello", "newworld")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
@@ -389,6 +493,8 @@ func TestRedis_SetExNx(t *testing.T) {
|
||||
ttl, err := client.Ttl("hello")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ttl > 0)
|
||||
_, err = NewRedis(client.Addr, "").SetnxEx("newhello", "newworld", 5)
|
||||
assert.NotNil(t, err)
|
||||
ok, err = client.SetnxEx("newhello", "newworld", 5)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, ok)
|
||||
@@ -408,12 +514,18 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := client.Hset("key", "field", "value")
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Hget("key", "field")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Hget("key", "field")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "value", val)
|
||||
_, err = NewRedis(client.Addr, "").Hexists("key", "field")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := client.Hexists("key", "field")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
_, err = NewRedis(client.Addr, "").Hdel("key", "field")
|
||||
assert.NotNil(t, err)
|
||||
ret, err := client.Hdel("key", "field")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ret)
|
||||
@@ -434,23 +546,50 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
val, err := client.Zscore("key", "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(2), val)
|
||||
_, err = NewRedis(client.Addr, "").Zincrby("key", 3, "value1")
|
||||
assert.NotNil(t, err)
|
||||
val, err = client.Zincrby("key", 3, "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(5), val)
|
||||
_, err = NewRedis(client.Addr, "").Zscore("key", "value1")
|
||||
assert.NotNil(t, err)
|
||||
val, err = client.Zscore("key", "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(5), val)
|
||||
ok, err = client.Zadd("key", 6, "value2")
|
||||
val, err = NewRedis(client.Addr, "").Zadds("key")
|
||||
assert.NotNil(t, err)
|
||||
val, err = client.Zadds("key", Pair{
|
||||
Key: "value2",
|
||||
Score: 6,
|
||||
}, Pair{
|
||||
Key: "value3",
|
||||
Score: 7,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
ok, err = client.Zadd("key", 7, "value3")
|
||||
assert.Equal(t, int64(2), val)
|
||||
pairs, err := NewRedis(client.Addr, "").ZRevRangeWithScores("key", 1, 3)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZRevRangeWithScores("key", 1, 3)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, []Pair{
|
||||
{
|
||||
Key: "value2",
|
||||
Score: 6,
|
||||
},
|
||||
{
|
||||
Key: "value1",
|
||||
Score: 5,
|
||||
},
|
||||
}, pairs)
|
||||
rank, err := client.Zrank("key", "value2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), rank)
|
||||
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.Zrank("key", "value4")
|
||||
assert.Equal(t, Nil, err)
|
||||
_, err = NewRedis(client.Addr, "").Zrem("key", "value2", "value3")
|
||||
assert.NotNil(t, err)
|
||||
num, err := client.Zrem("key", "value2", "value3")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
@@ -463,31 +602,47 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
ok, err = client.Zadd("key", 8, "value4")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
_, err = NewRedis(client.Addr, "").Zremrangebyscore("key", 6, 7)
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Zremrangebyscore("key", 6, 7)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
ok, err = client.Zadd("key", 6, "value2")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
_, err = NewRedis(client.Addr, "").Zadd("key", 7, "value3")
|
||||
assert.NotNil(t, err)
|
||||
ok, err = client.Zadd("key", 7, "value3")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
_, err = NewRedis(client.Addr, "").Zcount("key", 6, 7)
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Zcount("key", 6, 7)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
_, err = NewRedis(client.Addr, "").Zremrangebyrank("key", 1, 2)
|
||||
assert.NotNil(t, err)
|
||||
num, err = client.Zremrangebyrank("key", 1, 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, num)
|
||||
_, err = NewRedis(client.Addr, "").Zcard("key")
|
||||
assert.NotNil(t, err)
|
||||
card, err := client.Zcard("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, card)
|
||||
_, err = NewRedis(client.Addr, "").Zrange("key", 0, -1)
|
||||
assert.NotNil(t, err)
|
||||
vals, err := client.Zrange("key", 0, -1)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value1", "value4"}, vals)
|
||||
_, err = NewRedis(client.Addr, "").Zrevrange("key", 0, -1)
|
||||
assert.NotNil(t, err)
|
||||
vals, err = client.Zrevrange("key", 0, -1)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value4", "value1"}, vals)
|
||||
pairs, err := client.ZrangeWithScores("key", 0, -1)
|
||||
_, err = NewRedis(client.Addr, "").ZrangeWithScores("key", 0, -1)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZrangeWithScores("key", 0, -1)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []Pair{
|
||||
{
|
||||
@@ -499,6 +654,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 8,
|
||||
},
|
||||
}, pairs)
|
||||
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScores("key", 5, 8)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZrangebyscoreWithScores("key", 5, 8)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []Pair{
|
||||
@@ -511,6 +668,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 8,
|
||||
},
|
||||
}, pairs)
|
||||
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScoresAndLimit(
|
||||
"key", 5, 8, 1, 1)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []Pair{
|
||||
@@ -519,6 +679,11 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 8,
|
||||
},
|
||||
}, pairs)
|
||||
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(pairs))
|
||||
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScores("key", 5, 8)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZrevrangebyscoreWithScores("key", 5, 8)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []Pair{
|
||||
@@ -531,6 +696,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 5,
|
||||
},
|
||||
}, pairs)
|
||||
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScoresAndLimit(
|
||||
"key", 5, 8, 1, 1)
|
||||
assert.NotNil(t, err)
|
||||
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []Pair{
|
||||
@@ -539,11 +707,17 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 5,
|
||||
},
|
||||
}, pairs)
|
||||
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(pairs))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_Pipelined(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
assert.NotNil(t, NewRedis(client.Addr, "").Pipelined(func(pipeliner Pipeliner) error {
|
||||
return nil
|
||||
}))
|
||||
err := client.Pipelined(
|
||||
func(pipe Pipeliner) error {
|
||||
pipe.Incr("pipelined_counter")
|
||||
@@ -553,6 +727,8 @@ func TestRedis_Pipelined(t *testing.T) {
|
||||
},
|
||||
)
|
||||
assert.Nil(t, err)
|
||||
_, err = NewRedis(client.Addr, "").Ttl("pipelined_counter")
|
||||
assert.NotNil(t, err)
|
||||
ttl, err := client.Ttl("pipelined_counter")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3600, ttl)
|
||||
@@ -565,6 +741,76 @@ func TestRedis_Pipelined(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedisString(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
_, err := getRedis(NewRedis(client.Addr, ClusterType))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, client.Addr, client.String())
|
||||
assert.NotNil(t, NewRedis(client.Addr, "").Ping())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedisScriptLoad(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
_, err := NewRedis(client.Addr, "").scriptLoad("foo")
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.scriptLoad("foo")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedisToPairs(t *testing.T) {
|
||||
pairs := toPairs([]red.Z{
|
||||
{
|
||||
Member: 1,
|
||||
Score: 1,
|
||||
},
|
||||
{
|
||||
Member: 2,
|
||||
Score: 2,
|
||||
},
|
||||
})
|
||||
assert.EqualValues(t, []Pair{
|
||||
{
|
||||
Key: "1",
|
||||
Score: 1,
|
||||
},
|
||||
{
|
||||
Key: "2",
|
||||
Score: 2,
|
||||
},
|
||||
}, pairs)
|
||||
}
|
||||
|
||||
func TestRedisToStrings(t *testing.T) {
|
||||
vals := toStrings([]interface{}{1, 2})
|
||||
assert.EqualValues(t, []string{"1", "2"}, vals)
|
||||
}
|
||||
|
||||
func TestRedisBlpop(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
var node mockedNode
|
||||
_, err := client.Blpop(nil, "foo")
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.Blpop(node, "foo")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedisBlpopEx(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
var node mockedNode
|
||||
_, _, err := client.BlpopEx(nil, "foo")
|
||||
assert.NotNil(t, err)
|
||||
_, _, err = client.BlpopEx(node, "foo")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
||||
s, err := miniredis.Run()
|
||||
assert.Nil(t, err)
|
||||
@@ -576,8 +822,18 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
client.Close()
|
||||
if client != nil {
|
||||
client.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
fn(NewRedis(s.Addr(), NodeType))
|
||||
}
|
||||
|
||||
type mockedNode struct {
|
||||
RedisNode
|
||||
}
|
||||
|
||||
func (n mockedNode) BLPop(timeout time.Duration, keys ...string) *red.StringSliceCmd {
|
||||
return red.NewStringSliceCmd("foo", "bar")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/internal"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
@@ -19,7 +18,7 @@ var (
|
||||
|
||||
// can't use one SharedCalls per conn, because multiple conns may share the same cache key.
|
||||
exclusiveCalls = syncx.NewSharedCalls()
|
||||
stats = internal.NewCacheStat("sqlc")
|
||||
stats = cache.NewCacheStat("sqlc")
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -30,21 +29,21 @@ type (
|
||||
|
||||
CachedConn struct {
|
||||
db sqlx.SqlConn
|
||||
cache internal.Cache
|
||||
cache cache.Cache
|
||||
}
|
||||
)
|
||||
|
||||
func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
|
||||
return CachedConn{
|
||||
db: db,
|
||||
cache: internal.NewCacheNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
cache: cache.NewCacheNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
}
|
||||
}
|
||||
|
||||
func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
|
||||
return CachedConn{
|
||||
db: db,
|
||||
cache: internal.NewCache(c, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
cache: cache.NewCache(c, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +82,11 @@ func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary
|
||||
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
|
||||
var primaryKey interface{}
|
||||
var found bool
|
||||
|
||||
// if don't use convert numeric primary key into int64,
|
||||
// then it will be represented as scientific notion, like 2e6
|
||||
// which will make the cache doesn't match with the previous insert one
|
||||
keyer = floatKeyer(keyer)
|
||||
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
|
||||
primaryKey, err = indexQuery(cc.db, v)
|
||||
if err != nil {
|
||||
@@ -120,3 +124,16 @@ func (cc CachedConn) SetCache(key string, v interface{}) error {
|
||||
func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
|
||||
return cc.db.Transact(fn)
|
||||
}
|
||||
|
||||
func floatKeyer(fn func(interface{}) string) func(interface{}) string {
|
||||
return func(primary interface{}) string {
|
||||
switch v := primary.(type) {
|
||||
case float32:
|
||||
return fn(int64(v))
|
||||
case float64:
|
||||
return fn(int64(v))
|
||||
default:
|
||||
return fn(primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,103 @@ func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
||||
assert.Equal(t, `"xin"`, val)
|
||||
}
|
||||
|
||||
func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
|
||||
const (
|
||||
primaryInt8 int8 = 100
|
||||
primaryInt16 int16 = 10000
|
||||
primaryInt32 int32 = 10000000
|
||||
primaryInt64 int64 = 10000000
|
||||
primaryUint8 uint8 = 100
|
||||
primaryUint16 uint16 = 10000
|
||||
primaryUint32 uint32 = 10000000
|
||||
primaryUint64 uint64 = 10000000
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
primary interface{}
|
||||
primaryCache string
|
||||
}{
|
||||
{
|
||||
name: "int8 primary",
|
||||
primary: primaryInt8,
|
||||
primaryCache: fmt.Sprint(primaryInt8),
|
||||
},
|
||||
{
|
||||
name: "int16 primary",
|
||||
primary: primaryInt16,
|
||||
primaryCache: fmt.Sprint(primaryInt16),
|
||||
},
|
||||
{
|
||||
name: "int32 primary",
|
||||
primary: primaryInt32,
|
||||
primaryCache: fmt.Sprint(primaryInt32),
|
||||
},
|
||||
{
|
||||
name: "int64 primary",
|
||||
primary: primaryInt64,
|
||||
primaryCache: fmt.Sprint(primaryInt64),
|
||||
},
|
||||
{
|
||||
name: "uint8 primary",
|
||||
primary: primaryUint8,
|
||||
primaryCache: fmt.Sprint(primaryUint8),
|
||||
},
|
||||
{
|
||||
name: "uint16 primary",
|
||||
primary: primaryUint16,
|
||||
primaryCache: fmt.Sprint(primaryUint16),
|
||||
},
|
||||
{
|
||||
name: "uint32 primary",
|
||||
primary: primaryUint32,
|
||||
primaryCache: fmt.Sprint(primaryUint32),
|
||||
},
|
||||
{
|
||||
name: "uint64 primary",
|
||||
primary: primaryUint64,
|
||||
primaryCache: fmt.Sprint(primaryUint64),
|
||||
},
|
||||
}
|
||||
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resetStats()
|
||||
s.FlushAll()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||
cache.WithNotFoundExpiry(time.Second))
|
||||
|
||||
var str string
|
||||
r.Set("index", test.primaryCache)
|
||||
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
|
||||
return fmt.Sprintf("%v/1234", s)
|
||||
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
|
||||
assert.Fail(t, "should not go here")
|
||||
return test.primary, nil
|
||||
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
*v.(*string) = "xin"
|
||||
assert.Equal(t, primary, primary)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "xin", str)
|
||||
val, err := r.Get("index")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.primaryCache, val)
|
||||
val, err = r.Get(test.primaryCache + "/1234")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `"xin"`, val)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
||||
caches := map[string]string{
|
||||
"index": "primary",
|
||||
@@ -148,6 +245,8 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s.FlushAll()
|
||||
defer s.Close()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||
@@ -446,6 +545,45 @@ func TestCachedConnTransact(t *testing.T) {
|
||||
assert.True(t, conn.transactValue)
|
||||
}
|
||||
|
||||
func TestQueryRowNoCache(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
value = "any"
|
||||
)
|
||||
var user string
|
||||
var ran bool
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
|
||||
user = value
|
||||
ran = true
|
||||
return nil
|
||||
}}
|
||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||
err = c.QueryRowNoCache(&user, key)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, value, user)
|
||||
assert.True(t, ran)
|
||||
}
|
||||
|
||||
func TestFloatKeyer(t *testing.T) {
|
||||
primaries := []interface{}{
|
||||
float32(1),
|
||||
float64(1),
|
||||
}
|
||||
|
||||
for _, primary := range primaries {
|
||||
val := floatKeyer(func(i interface{}) string {
|
||||
return fmt.Sprint(i)
|
||||
})(primary)
|
||||
assert.Equal(t, "1", val)
|
||||
}
|
||||
}
|
||||
|
||||
func resetStats() {
|
||||
atomic.StoreUint64(&stats.Total, 0)
|
||||
atomic.StoreUint64(&stats.Hit, 0)
|
||||
@@ -454,6 +592,7 @@ func resetStats() {
|
||||
}
|
||||
|
||||
type dummySqlConn struct {
|
||||
queryRow func(interface{}, string, ...interface{}) error
|
||||
}
|
||||
|
||||
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
@@ -465,6 +604,9 @@ func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
|
||||
if d.queryRow != nil {
|
||||
return d.queryRow(v, query, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,18 @@ func TestUnmarshalRowBool(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowBoolNotSettable(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
var value bool
|
||||
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select value from users where user=?", "anyone"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
|
||||
@@ -228,6 +240,40 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
|
||||
var value = new(struct {
|
||||
Age *int `db:"age"`
|
||||
Name string `db:"name"`
|
||||
})
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name"}).FromCSVString("liao")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowStructWithTagsPtr(t *testing.T) {
|
||||
var value = new(struct {
|
||||
Age *int `db:"age"`
|
||||
Name string `db:"name"`
|
||||
})
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.Nil(t, query(db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"))
|
||||
assert.Equal(t, "liao", value.Name)
|
||||
assert.Equal(t, 5, *value.Age)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsBool(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var expect = []bool{true, false}
|
||||
|
||||
@@ -33,35 +33,42 @@ func NewSharedCalls() SharedCalls {
|
||||
}
|
||||
|
||||
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
|
||||
g.lock.Lock()
|
||||
if c, ok := g.calls[key]; ok {
|
||||
g.lock.Unlock()
|
||||
c.wg.Wait()
|
||||
c, done := g.createCall(key, fn)
|
||||
if done {
|
||||
return c.val, c.err
|
||||
}
|
||||
|
||||
c := g.makeCall(key, fn)
|
||||
g.makeCall(c, key, fn)
|
||||
return c.val, c.err
|
||||
}
|
||||
|
||||
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
|
||||
c, done := g.createCall(key, fn)
|
||||
if done {
|
||||
return c.val, false, c.err
|
||||
}
|
||||
|
||||
g.makeCall(c, key, fn)
|
||||
return c.val, true, c.err
|
||||
}
|
||||
|
||||
func (g *sharedGroup) createCall(key string, fn func() (interface{}, error)) (c *call, done bool) {
|
||||
g.lock.Lock()
|
||||
if c, ok := g.calls[key]; ok {
|
||||
g.lock.Unlock()
|
||||
c.wg.Wait()
|
||||
return c.val, false, c.err
|
||||
return c, true
|
||||
}
|
||||
|
||||
c := g.makeCall(key, fn)
|
||||
return c.val, true, c.err
|
||||
}
|
||||
|
||||
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
|
||||
c := new(call)
|
||||
c = new(call)
|
||||
c.wg.Add(1)
|
||||
g.calls[key] = c
|
||||
g.lock.Unlock()
|
||||
|
||||
return c, false
|
||||
}
|
||||
|
||||
func (g *sharedGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
|
||||
defer func() {
|
||||
// delete key first, done later. can't reverse the order, because if reverse,
|
||||
// another Do call might wg.Wait() without get notified with wg.Done()
|
||||
@@ -72,5 +79,4 @@ func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call
|
||||
}()
|
||||
|
||||
c.val, c.err = fn()
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ And now, let’s walk through the complete flow of quickly create a microservice
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* create the working dir bookstore
|
||||
* create the working dir `bookstore` and `bookstore/api`
|
||||
|
||||
* in `bookstore` dir, execute `go mod init bookstore` to initialize `go.mod``
|
||||
|
||||
@@ -191,6 +191,8 @@ And now, let’s walk through the complete flow of quickly create a microservice
|
||||
|
||||
## 6. Write code for add rpc service
|
||||
|
||||
- under directory `bookstore` create dir `rpc`
|
||||
|
||||
* under directory `rpc/add` create `add.proto` file
|
||||
|
||||
```shell
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* 创建工作目录`bookstore`
|
||||
* 创建工作目录 `bookstore` 和 `bookstore/api`
|
||||
|
||||
* 在`bookstore`目录下执行`go mod init bookstore`初始化`go.mod`
|
||||
|
||||
@@ -191,6 +191,8 @@
|
||||
|
||||
## 6. 编写add rpc服务
|
||||
|
||||
- 在 `bookstore` 下创建 `rpc` 目录
|
||||
|
||||
* 在`rpc/add`目录下编写`add.proto`文件
|
||||
|
||||
可以通过命令生成proto文件模板
|
||||
@@ -546,7 +548,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
至此代码修改完成,凡事手动修改的代码我加了标注
|
||||
至此代码修改完成,凡是手动修改的代码我加了标注
|
||||
|
||||
## 11. 完整调用演示
|
||||
|
||||
|
||||
16
doc/goctl.md
16
doc/goctl.md
@@ -95,20 +95,20 @@ service user-api {
|
||||
)
|
||||
@server(
|
||||
handler: GetUserHandler
|
||||
folder: user
|
||||
group: user
|
||||
)
|
||||
get /api/user/:name(getRequest) returns(getResponse)
|
||||
|
||||
@server(
|
||||
handler: CreateUserHandler
|
||||
folder: user
|
||||
group: user
|
||||
)
|
||||
post /api/users/create(createRequest)
|
||||
}
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
folder: profile
|
||||
group: profile
|
||||
)
|
||||
service user-api {
|
||||
@doc(summary: user title)
|
||||
@@ -135,7 +135,7 @@ service user-api {
|
||||
|
||||
1. info部分:描述了api基本信息,比如Auth,api是哪个用途。
|
||||
2. type部分:type类型声明和golang语法兼容。
|
||||
3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置jwt和auth认证,另外通过folder属性可以指定service生成所在子目录。
|
||||
3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置jwt和auth认证,另外通过group属性可以指定service生成所在子目录。
|
||||
service里面包含api路由,比如上面第一组service的第一个路由,doc用来描述此路由的用途,GetProfileHandler表示处理这个路由的handler,
|
||||
`get /api/profile/:name(getRequest) returns(getResponse)` 中get代表api的请求方式(get/post/put/delete), `/api/profile/:name` 描述了路由path,`:name`通过
|
||||
请求getRequest里面的属性赋值,getResponse为返回的结构体,这两个类型都定义在2描述的类型中。
|
||||
@@ -239,10 +239,10 @@ src 示例代码如下
|
||||
```
|
||||
|
||||
结构体中不需要提供Id,CreateTime,UpdateTime三个字段,会自动生成
|
||||
结构体中每个tag有两个可选标签 c 和 o
|
||||
c 是改字段的注释
|
||||
o 是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
|
||||
生成的目标文件会覆盖该简单go文件
|
||||
结构体中每个tag有两个可选标签 c 和 o
|
||||
c 是该字段的注释
|
||||
o 是该字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
|
||||
生成的目标文件会覆盖该简单go文件
|
||||
|
||||
## goctl rpc生成(业务剥离中,暂未开放)
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 141 KiB |
@@ -106,10 +106,10 @@ func main() {
|
||||
|
||||
// 没有拿到结果,则调用makeCall方法去获取资源,注意此处仍然是锁住的,可以保证只有一个goroutine可以调用makecall
|
||||
c := g.makeCall(key, fn)
|
||||
// 返回调用结果
|
||||
// 返回调用结果
|
||||
return c.val, c.err
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
- sharedGroup的DoEx方法
|
||||
|
||||
@@ -160,7 +160,7 @@ func main() {
|
||||
c.val, c.err = fn()
|
||||
return c
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## 最后
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ And now, let’s walk through the complete flow of quickly create a microservice
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* create the working dir `shorturl`
|
||||
* create the working dir `shorturl` and `shorturl/api`
|
||||
|
||||
* in `shorturl` dir, execute `go mod init shorturl` to initialize `go.mod`
|
||||
|
||||
@@ -195,6 +195,8 @@ And now, let’s walk through the complete flow of quickly create a microservice
|
||||
|
||||
## 6. Write code for transform rpc service
|
||||
|
||||
- under directory `shorturl` create dir `rpc`
|
||||
|
||||
* under directory `rpc/transform` create `transform.proto` file
|
||||
|
||||
```shell
|
||||
|
||||
@@ -72,13 +72,19 @@
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* 创建工作目录`shorturl`
|
||||
* 创建工作目录 `shorturl` 和 `shorturl/api`
|
||||
|
||||
* 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod`
|
||||
|
||||
## 5. 编写API Gateway代码
|
||||
|
||||
* 通过goctl生成`api/shorturl.api`并编辑,为了简洁,去除了文件开头的`info`,代码如下:
|
||||
* 在`shorturl/api`目录下通过goctl生成`api/shorturl.api`:
|
||||
|
||||
```shell
|
||||
goctl api -o shorturl.api
|
||||
```
|
||||
|
||||
* 编辑`api/shorturl.api`,为了简洁,去除了文件开头的`info`,代码如下:
|
||||
|
||||
```go
|
||||
type (
|
||||
@@ -189,6 +195,8 @@
|
||||
|
||||
## 6. 编写transform rpc服务
|
||||
|
||||
- 在 `shorturl` 目录下创建 `rpc` 目录
|
||||
|
||||
* 在`rpc/transform`目录下编写`transform.proto`文件
|
||||
|
||||
可以通过命令生成proto文件模板
|
||||
@@ -474,7 +482,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
至此代码修改完成,凡事手动修改的代码我加了标注
|
||||
至此代码修改完成,凡是手动修改的代码我加了标注
|
||||
|
||||
## 10. 完整调用演示
|
||||
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
type (
|
||||
addReq struct {
|
||||
book string `form:"book"`
|
||||
price int64 `form:"price"`
|
||||
}
|
||||
addReq struct {
|
||||
book string `form:"book"`
|
||||
price int64 `form:"price"`
|
||||
}
|
||||
|
||||
addResp struct {
|
||||
ok bool `json:"ok"`
|
||||
}
|
||||
addResp struct {
|
||||
ok bool `json:"ok"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
checkReq struct {
|
||||
book string `form:"book"`
|
||||
}
|
||||
checkReq struct {
|
||||
book string `form:"book"`
|
||||
}
|
||||
|
||||
checkResp struct {
|
||||
found bool `json:"found"`
|
||||
price int64 `json:"price"`
|
||||
}
|
||||
checkResp struct {
|
||||
found bool `json:"found"`
|
||||
price int64 `json:"price"`
|
||||
}
|
||||
)
|
||||
|
||||
service bookstore-api {
|
||||
@server(
|
||||
handler: AddHandler
|
||||
)
|
||||
get /add(addReq) returns(addResp)
|
||||
get /add (addReq) returns (addResp)
|
||||
|
||||
@server(
|
||||
handler: CheckHandler
|
||||
)
|
||||
get /check(checkReq) returns(checkResp)
|
||||
get /check (checkReq) returns (checkResp)
|
||||
}
|
||||
@@ -19,7 +19,7 @@ type Roster struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
model := mongo.MustNewModel("localhost:27017", "blackboard", "roster")
|
||||
model := mongo.MustNewModel("localhost:27017/blackboard", "roster")
|
||||
for i := 0; i < 1000; i++ {
|
||||
session, err := model.TakeSession()
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -50,5 +51,13 @@ func main() {
|
||||
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
|
||||
unary.RegisterGreeterServer(grpcServer, NewGreetServer())
|
||||
})
|
||||
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
st := time.Now()
|
||||
resp, err = handler(ctx, req)
|
||||
log.Printf("method: %s time: %v\n", info.FullMethod, time.Since(st))
|
||||
return resp, err
|
||||
}
|
||||
|
||||
server.AddUnaryInterceptors(interceptor)
|
||||
server.Start()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ English | [简体中文](readme.md)
|
||||
|
||||
[](https://github.com/tal-tech/go-zero/actions)
|
||||
[](https://codecov.io/gh/tal-tech/go-zero)
|
||||
[](https://goreportcard.com/report/github.com/tal-tech/go-zero)
|
||||
[](https://github.com/tal-tech/go-zero)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
|
||||
28
readme.md
28
readme.md
@@ -4,6 +4,7 @@
|
||||
|
||||
[](https://github.com/tal-tech/go-zero/actions)
|
||||
[](https://codecov.io/gh/tal-tech/go-zero)
|
||||
[](https://goreportcard.com/report/github.com/tal-tech/go-zero)
|
||||
[](https://github.com/tal-tech/go-zero)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
@@ -148,18 +149,23 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
## 8. 文档 (逐步完善中)
|
||||
## 8. 文档
|
||||
|
||||
* [快速构建高并发微服务](doc/shorturl.md)
|
||||
* [快速构建高并发微服务-多RPC版](doc/bookstore.md)
|
||||
* [goctl使用帮助](doc/goctl.md)
|
||||
* [通过MapReduce降低服务响应时间](doc/mapreduce.md)
|
||||
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
||||
* [进程内缓存使用方法](doc/collection.md)
|
||||
* [防止缓存击穿之进程内共享调用](doc/sharedcalls.md)
|
||||
* [基于prometheus的微服务指标监控](doc/metric.md)
|
||||
* [文本序列化和反序列化](doc/mapping.md)
|
||||
* [快速构建jwt鉴权认证](doc/jwt.md)
|
||||
* API文档 (逐步完善中)
|
||||
|
||||
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
||||
|
||||
* awesome系列
|
||||
* [快速构建高并发微服务](doc/shorturl.md)
|
||||
* [快速构建高并发微服务-多RPC版](doc/bookstore.md)
|
||||
* [goctl使用帮助](doc/goctl.md)
|
||||
* [通过MapReduce降低服务响应时间](doc/mapreduce.md)
|
||||
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
||||
* [进程内缓存使用方法](doc/collection.md)
|
||||
* [防止缓存击穿之进程内共享调用](doc/sharedcalls.md)
|
||||
* [基于prometheus的微服务指标监控](doc/metric.md)
|
||||
* [文本序列化和反序列化](doc/mapping.md)
|
||||
* [快速构建jwt鉴权认证](doc/jwt.md)
|
||||
|
||||
## 9. 微信交流群
|
||||
|
||||
|
||||
@@ -27,6 +27,13 @@ func TestParseForm(t *testing.T) {
|
||||
assert.Equal(t, 3.4, v.Percent)
|
||||
}
|
||||
|
||||
func TestParseHeader(t *testing.T) {
|
||||
m := ParseHeader("key=value;")
|
||||
assert.EqualValues(t, map[string]string{
|
||||
"key": "value",
|
||||
}, m)
|
||||
}
|
||||
|
||||
func TestParseFormOutOfRange(t *testing.T) {
|
||||
var v struct {
|
||||
Age int `form:"age,range=[10:20)"`
|
||||
|
||||
@@ -183,9 +183,9 @@ func getHandlerBaseName(handler string) string {
|
||||
}
|
||||
|
||||
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
return handlerDir
|
||||
}
|
||||
|
||||
@@ -113,9 +113,9 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
}
|
||||
|
||||
func getLogicFolderPath(group spec.Group, route spec.Route) string {
|
||||
folder, ok := util.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
folder, ok := util.GetAnnotationValue(route.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
folder, ok = util.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
folder, ok = util.GetAnnotationValue(group.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
return logicDir
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
||||
importSet.AddStr(fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
|
||||
for _, group := range api.Service.Groups {
|
||||
for _, route := range group.Routes {
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -165,11 +165,11 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
return nil, fmt.Errorf("missing handler annotation for route %q", r.Path)
|
||||
}
|
||||
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
|
||||
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", folderProperty)
|
||||
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
} else {
|
||||
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", folderProperty)
|
||||
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gogen
|
||||
|
||||
const (
|
||||
interval = "internal/"
|
||||
typesPacket = "types"
|
||||
configDir = interval + "config"
|
||||
contextDir = interval + "svc"
|
||||
handlerDir = interval + "handler"
|
||||
logicDir = interval + "logic"
|
||||
typesDir = interval + typesPacket
|
||||
folderProperty = "folder"
|
||||
interval = "internal/"
|
||||
typesPacket = "types"
|
||||
configDir = interval + "config"
|
||||
contextDir = interval + "svc"
|
||||
handlerDir = interval + "handler"
|
||||
logicDir = interval + "logic"
|
||||
typesDir = interval + typesPacket
|
||||
groupProperty = "group"
|
||||
)
|
||||
|
||||
@@ -88,15 +88,17 @@ func (p *serviceEntityParser) parseLine(line string, api *spec.ApiSpec, annos []
|
||||
}
|
||||
}
|
||||
|
||||
if len(fields) < 3 {
|
||||
if len(fields) < 2 {
|
||||
return defaultErr
|
||||
}
|
||||
|
||||
method := fields[0]
|
||||
path := fields[1]
|
||||
req := fields[2]
|
||||
var req string
|
||||
var resp string
|
||||
|
||||
if len(fields) > 2 {
|
||||
req = fields[2]
|
||||
}
|
||||
if stringx.Contains(fields, returnsTag) {
|
||||
if fields[len(fields)-1] != returnsTag {
|
||||
resp = fields[len(fields)-1]
|
||||
|
||||
@@ -90,21 +90,20 @@ func MatchStruct(api string) (*ApiStruct, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "import") {
|
||||
if isImportBeginLine(line) {
|
||||
parseImport = true
|
||||
}
|
||||
if parseImport && (strings.HasPrefix(line, "type") || strings.HasPrefix(line, "@server") ||
|
||||
strings.HasPrefix(line, "service")) {
|
||||
if parseImport && (isTypeBeginLine(line) || isServiceBeginLine(line)) {
|
||||
parseImport = false
|
||||
result.Imports = segment
|
||||
segment = line + "\n"
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "type") {
|
||||
if isTypeBeginLine(line) {
|
||||
parseType = true
|
||||
}
|
||||
if strings.HasPrefix(line, "@server") || strings.HasPrefix(line, "service") {
|
||||
if isServiceBeginLine(line) {
|
||||
if parseType {
|
||||
parseType = false
|
||||
result.StructBody = segment
|
||||
@@ -122,3 +121,15 @@ func MatchStruct(api string) (*ApiStruct, error) {
|
||||
result.Service = segment
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func isImportBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "import") && strings.HasSuffix(line, ".api")
|
||||
}
|
||||
|
||||
func isTypeBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "type")
|
||||
}
|
||||
|
||||
func isServiceBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "@server(") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
||||
}
|
||||
|
||||
@@ -82,20 +82,20 @@ service user-api {
|
||||
)
|
||||
@server(
|
||||
handler: GetUserHandler
|
||||
folder: user
|
||||
group: user
|
||||
)
|
||||
get /api/user/:name(getRequest) returns(getResponse)
|
||||
|
||||
@server(
|
||||
handler: CreateUserHandler
|
||||
folder: user
|
||||
group: user
|
||||
)
|
||||
post /api/users/create(createRequest)
|
||||
}
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
folder: profile
|
||||
group: profile
|
||||
)
|
||||
service user-api {
|
||||
@doc(summary: user title)
|
||||
@@ -121,7 +121,7 @@ service user-api {
|
||||
|
||||
1. info部分:描述了api基本信息,比如Auth,api是哪个用途。
|
||||
2. type部分:type类型声明和golang语法兼容。
|
||||
3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置folder属性来指定service生成所在子目录。
|
||||
3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置group属性来指定service生成所在子目录。
|
||||
service里面包含api路由,比如上面第一组service的第一个路由,doc用来描述此路由的用途,GetProfileHandler表示处理这个路由的handler,
|
||||
`get /api/profile/:name(getRequest) returns(getResponse)` 中get代表api的请求方式(get/post/put/delete), `/api/profile/:name` 描述了路由path,`:name`通过
|
||||
请求getRequest里面的属性赋值,getResponse为返回的结构体,这两个类型都定义在2描述的类型中。
|
||||
|
||||
@@ -16,7 +16,10 @@ var (
|
||||
)
|
||||
|
||||
type (
|
||||
ClientOption = internal.ClientOption
|
||||
|
||||
Client interface {
|
||||
AddInterceptor(interceptor grpc.UnaryClientInterceptor)
|
||||
Conn() *grpc.ClientConn
|
||||
}
|
||||
|
||||
@@ -25,7 +28,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func MustNewClient(c RpcClientConf, options ...internal.ClientOption) Client {
|
||||
func MustNewClient(c RpcClientConf, options ...ClientOption) Client {
|
||||
cli, err := NewClient(c, options...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -34,8 +37,8 @@ func MustNewClient(c RpcClientConf, options ...internal.ClientOption) Client {
|
||||
return cli
|
||||
}
|
||||
|
||||
func NewClient(c RpcClientConf, options ...internal.ClientOption) (Client, error) {
|
||||
var opts []internal.ClientOption
|
||||
func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
|
||||
var opts []ClientOption
|
||||
if c.HasCredential() {
|
||||
opts = append(opts, WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{
|
||||
App: c.App,
|
||||
@@ -74,10 +77,14 @@ func NewClientNoAuth(c discov.EtcdConf) (Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewClientWithTarget(target string, opts ...internal.ClientOption) (Client, error) {
|
||||
func NewClientWithTarget(target string, opts ...ClientOption) (Client, error) {
|
||||
return internal.NewClient(target, opts...)
|
||||
}
|
||||
|
||||
func (rc *RpcClient) AddInterceptor(interceptor grpc.UnaryClientInterceptor) {
|
||||
rc.client.AddInterceptor(interceptor)
|
||||
}
|
||||
|
||||
func (rc *RpcClient) Conn() *grpc.ClientConn {
|
||||
return rc.client.Conn()
|
||||
}
|
||||
|
||||
@@ -31,37 +31,30 @@ type (
|
||||
ClientOption func(options *ClientOptions)
|
||||
|
||||
client struct {
|
||||
conn *grpc.ClientConn
|
||||
conn *grpc.ClientConn
|
||||
interceptors []grpc.UnaryClientInterceptor
|
||||
}
|
||||
)
|
||||
|
||||
func NewClient(target string, opts ...ClientOption) (*client, error) {
|
||||
var cli client
|
||||
opts = append(opts, WithDialOption(grpc.WithBalancerName(p2c.Name)))
|
||||
conn, err := dial(target, opts...)
|
||||
if err != nil {
|
||||
if err := cli.dial(target, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client{conn: conn}, nil
|
||||
return &cli, nil
|
||||
}
|
||||
|
||||
func (c *client) AddInterceptor(interceptor grpc.UnaryClientInterceptor) {
|
||||
c.interceptors = append(c.interceptors, interceptor)
|
||||
}
|
||||
|
||||
func (c *client) Conn() *grpc.ClientConn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
func WithDialOption(opt grpc.DialOption) ClientOption {
|
||||
return func(options *ClientOptions) {
|
||||
options.DialOptions = append(options.DialOptions, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) ClientOption {
|
||||
return func(options *ClientOptions) {
|
||||
options.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func buildDialOptions(opts ...ClientOption) []grpc.DialOption {
|
||||
func (c *client) buildDialOptions(opts ...ClientOption) []grpc.DialOption {
|
||||
var clientOptions ClientOptions
|
||||
for _, opt := range opts {
|
||||
opt(&clientOptions)
|
||||
@@ -78,12 +71,15 @@ func buildDialOptions(opts ...ClientOption) []grpc.DialOption {
|
||||
clientinterceptors.TimeoutInterceptor(clientOptions.Timeout),
|
||||
),
|
||||
}
|
||||
for _, interceptor := range c.interceptors {
|
||||
options = append(options, WithUnaryClientInterceptors(interceptor))
|
||||
}
|
||||
|
||||
return append(options, clientOptions.DialOptions...)
|
||||
}
|
||||
|
||||
func dial(server string, opts ...ClientOption) (*grpc.ClientConn, error) {
|
||||
options := buildDialOptions(opts...)
|
||||
func (c *client) dial(server string, opts ...ClientOption) error {
|
||||
options := c.buildDialOptions(opts...)
|
||||
timeCtx, cancel := context.WithTimeout(context.Background(), dialTimeout)
|
||||
defer cancel()
|
||||
conn, err := grpc.DialContext(timeCtx, server, options...)
|
||||
@@ -96,9 +92,22 @@ func dial(server string, opts ...ClientOption) (*grpc.ClientConn, error) {
|
||||
service = server[pos+1:]
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("rpc dial: %s, error: %s, make sure rpc service %q is alread started",
|
||||
return fmt.Errorf("rpc dial: %s, error: %s, make sure rpc service %q is alread started",
|
||||
server, err.Error(), service)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
c.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithDialOption(opt grpc.DialOption) ClientOption {
|
||||
return func(options *ClientOptions) {
|
||||
options.DialOptions = append(options.DialOptions, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) ClientOption {
|
||||
return func(options *ClientOptions) {
|
||||
options.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ func TestWithTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildDialOptions(t *testing.T) {
|
||||
var c client
|
||||
agent := grpc.WithUserAgent("chrome")
|
||||
opts := buildDialOptions(WithDialOption(agent))
|
||||
opts := c.buildDialOptions(WithDialOption(agent))
|
||||
assert.Contains(t, opts, agent)
|
||||
}
|
||||
|
||||
@@ -67,15 +67,15 @@ func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error
|
||||
}
|
||||
|
||||
func (rs *RpcServer) AddOptions(options ...grpc.ServerOption) {
|
||||
rs.AddOptions(options...)
|
||||
rs.server.AddOptions(options...)
|
||||
}
|
||||
|
||||
func (rs *RpcServer) AddStreamInterceptors(interceptors ...grpc.StreamServerInterceptor) {
|
||||
rs.AddStreamInterceptors(interceptors...)
|
||||
rs.server.AddStreamInterceptors(interceptors...)
|
||||
}
|
||||
|
||||
func (rs *RpcServer) AddUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) {
|
||||
rs.AddUnaryInterceptors(interceptors...)
|
||||
rs.server.AddUnaryInterceptors(interceptors...)
|
||||
}
|
||||
|
||||
func (rs *RpcServer) Start() {
|
||||
|
||||
Reference in New Issue
Block a user