commit
504b0efd66
16
cache/interface.go
vendored
Normal file
16
cache/interface.go
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cache[K comparable, V any] interface {
|
||||
Get(ctx context.Context, key K) (V, bool)
|
||||
Set(ctx context.Context, key K, val V, expire time.Duration)
|
||||
Ttl(ctx context.Context, key K, expire time.Duration) time.Duration
|
||||
Ver(ctx context.Context, key K) int
|
||||
Flush(ctx context.Context)
|
||||
Delete(ctx context.Context, key K)
|
||||
ClearExpired(ctx context.Context, expire time.Duration)
|
||||
}
|
195
cache/map.go
vendored
195
cache/map.go
vendored
|
@ -4,42 +4,36 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MapCache[K comparable, V any] struct {
|
||||
data safety.Map[K, mapCacheStruct[V]]
|
||||
mutex *sync.Mutex
|
||||
data Cache[K, V]
|
||||
mux sync.Mutex
|
||||
cacheFunc func(...any) (V, error)
|
||||
batchCacheFn func(...any) (map[K]V, error)
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func NewMapCache[K comparable, V any](expireTime time.Duration) *MapCache[K, V] {
|
||||
return &MapCache[K, V]{expireTime: expireTime}
|
||||
}
|
||||
|
||||
type mapCacheStruct[T any] struct {
|
||||
setTime time.Time
|
||||
incr int
|
||||
data T
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) {
|
||||
m.cacheFunc = fn
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetLastSetTime(k K) (t time.Time) {
|
||||
r, ok := m.data.Load(k)
|
||||
if ok {
|
||||
t = r.setTime
|
||||
}
|
||||
return
|
||||
func (m *MapCache[K, V]) Ttl(ctx context.Context, k K) time.Duration {
|
||||
return m.data.Ttl(ctx, k, m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetCacheBatchFunc(fn func(...any) (map[K]V, error)) {
|
||||
func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) {
|
||||
tt := m.data.Ttl(ctx, k, m.expireTime)
|
||||
if tt <= 0 {
|
||||
return
|
||||
}
|
||||
return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime)).Add(-m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetCacheBatchFn(fn func(...any) (map[K]V, error)) {
|
||||
m.batchCacheFn = fn
|
||||
if m.cacheFunc == nil {
|
||||
m.setCacheFn(fn)
|
||||
|
@ -68,102 +62,56 @@ func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) {
|
|||
}
|
||||
}
|
||||
|
||||
func NewMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
func NewMapCacheByFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
return &MapCache[K, V]{
|
||||
mutex: &sync.Mutex{},
|
||||
mux: sync.Mutex{},
|
||||
cacheFunc: fn,
|
||||
expireTime: expireTime,
|
||||
data: safety.NewMap[K, mapCacheStruct[V]](),
|
||||
data: cacheType,
|
||||
}
|
||||
}
|
||||
func NewMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
func NewMapCacheByBatchFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
r := &MapCache[K, V]{
|
||||
mutex: &sync.Mutex{},
|
||||
mux: sync.Mutex{},
|
||||
batchCacheFn: fn,
|
||||
expireTime: expireTime,
|
||||
data: safety.NewMap[K, mapCacheStruct[V]](),
|
||||
data: cacheType,
|
||||
}
|
||||
r.setCacheFn(fn)
|
||||
return r
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Flush() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.data = safety.NewMap[K, mapCacheStruct[V]]()
|
||||
func (m *MapCache[K, V]) Flush(ctx context.Context) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
m.data.Flush(ctx)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Get(k K) (V, bool) {
|
||||
r, ok := m.data.Load(k)
|
||||
if ok {
|
||||
return r.data, ok
|
||||
}
|
||||
var rr V
|
||||
return rr, false
|
||||
func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) {
|
||||
return m.data.Get(ctx, k)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Set(k K, v V) {
|
||||
m.set(k, v)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetByBatchFn(params ...any) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
r, err := m.batchCacheFn(params...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range r {
|
||||
m.set(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) set(k K, v V) {
|
||||
data, ok := m.data.Load(k)
|
||||
t := time.Now()
|
||||
if !ok {
|
||||
data.data = v
|
||||
data.setTime = t
|
||||
data.incr++
|
||||
m.data.Store(k, data)
|
||||
} else {
|
||||
m.data.Store(k, mapCacheStruct[V]{
|
||||
data: v,
|
||||
setTime: t,
|
||||
})
|
||||
|
||||
}
|
||||
func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) {
|
||||
m.data.Set(ctx, k, v, m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) {
|
||||
data, ok := m.data.Load(key)
|
||||
if !ok {
|
||||
data = mapCacheStruct[V]{}
|
||||
}
|
||||
now := time.Duration(time.Now().UnixNano())
|
||||
data, ok := m.data.Get(c, key)
|
||||
var err error
|
||||
expired := time.Duration(data.setTime.UnixNano())+m.expireTime < now
|
||||
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢?
|
||||
if !ok || (ok && m.expireTime >= 0 && expired) {
|
||||
t := data.incr
|
||||
if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 {
|
||||
ver := m.data.Ver(c, key)
|
||||
call := func() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
da, ok := m.data.Load(key)
|
||||
if ok && da.incr > t {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
if m.data.Ver(c, key) > ver {
|
||||
data, _ = m.data.Get(c, key)
|
||||
return
|
||||
} else {
|
||||
da = data
|
||||
}
|
||||
r, er := m.cacheFunc(params...)
|
||||
data, err = m.cacheFunc(params...)
|
||||
if err != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
m.set(key, r)
|
||||
data.data = r
|
||||
m.Set(c, key, data)
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
|
@ -183,48 +131,42 @@ func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duratio
|
|||
}
|
||||
|
||||
}
|
||||
return data.data, err
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
var needFlush []K
|
||||
var res []V
|
||||
t := 0
|
||||
now := time.Duration(time.Now().UnixNano())
|
||||
for _, k := range key {
|
||||
d, ok := m.data.Load(k)
|
||||
if !ok {
|
||||
needFlush = append(needFlush, k)
|
||||
continue
|
||||
}
|
||||
expired := time.Duration(d.setTime.UnixNano())+m.expireTime < now
|
||||
if expired {
|
||||
needFlush = append(needFlush, k)
|
||||
}
|
||||
t = t + d.incr
|
||||
ver := 0
|
||||
needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
|
||||
if _, ok := m.data.Get(c, k); !ok {
|
||||
return k, true
|
||||
}
|
||||
ver += m.data.Ver(c, k)
|
||||
return
|
||||
})
|
||||
|
||||
var err error
|
||||
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢?
|
||||
if len(needFlush) > 0 {
|
||||
call := func() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
tt := 0
|
||||
for _, dd := range needFlush {
|
||||
if ddd, ok := m.data.Load(dd); ok {
|
||||
tt = tt + ddd.incr
|
||||
}
|
||||
}
|
||||
if tt > t {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
|
||||
vers := slice.Reduce(needFlush, func(t K, r int) int {
|
||||
r += m.data.Ver(c, t)
|
||||
return r
|
||||
}, 0)
|
||||
|
||||
if vers > ver {
|
||||
return
|
||||
}
|
||||
|
||||
r, er := m.batchCacheFn(params...)
|
||||
if err != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k, v := range r {
|
||||
m.set(k, v)
|
||||
m.Set(c, k, v)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
|
@ -244,23 +186,14 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.
|
|||
call()
|
||||
}
|
||||
}
|
||||
for _, k := range key {
|
||||
d, ok := m.data.Load(k)
|
||||
if ok {
|
||||
res = append(res, d.data)
|
||||
}
|
||||
}
|
||||
res = slice.FilterAndMap(key, func(k K) (V, bool) {
|
||||
return m.data.Get(c, k)
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) ClearExpired() {
|
||||
now := time.Duration(time.Now().UnixNano())
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.data.Range(func(k K, v mapCacheStruct[V]) bool {
|
||||
if now > time.Duration(v.setTime.UnixNano())+m.expireTime {
|
||||
m.data.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
m.data.ClearExpired(ctx, m.expireTime)
|
||||
}
|
||||
|
|
370
cache/map_test.go
vendored
Normal file
370
cache/map_test.go
vendored
Normal file
|
@ -0,0 +1,370 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ca MapCache[string, string]
|
||||
var fn func(a ...any) (string, error)
|
||||
var batchFn func(a ...any) (map[string]string, error)
|
||||
var ct context.Context
|
||||
|
||||
func init() {
|
||||
fn = func(a ...any) (string, error) {
|
||||
aa := a[1].(string)
|
||||
return strings.Repeat(aa, 2), nil
|
||||
}
|
||||
ct = context.Background()
|
||||
batchFn = func(a ...any) (map[string]string, error) {
|
||||
arr := a[1].([]string)
|
||||
return slice.SimpleToMap(arr, func(t string) string {
|
||||
return strings.Repeat(t, 2)
|
||||
}), nil
|
||||
}
|
||||
ca = *NewMemoryMapCacheByFn[string, string](fn, time.Second*2)
|
||||
ca.SetCacheBatchFn(batchFn)
|
||||
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
|
||||
_, _ = ca.GetCache(ct, "bb", time.Second, ct, "bb")
|
||||
}
|
||||
func TestMapCache_ClearExpired(t *testing.T) {
|
||||
type args struct {
|
||||
ct context.Context
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args{
|
||||
ct: ct,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(ca.Get(ct, "aa"))
|
||||
fmt.Println(ca.Get(ct, "bb"))
|
||||
time.Sleep(time.Second * 3)
|
||||
tt.m.ClearExpired(tt.args.ct)
|
||||
fmt.Println(ca.Get(ct, "bb"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_Flush(t *testing.T) {
|
||||
type args struct {
|
||||
ct context.Context
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args
|
||||
}
|
||||
ca := *NewMemoryMapCacheByFn[string, string](fn, time.Second)
|
||||
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args{
|
||||
ct,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(ca.Get(ct, "aa"))
|
||||
tt.m.Flush(tt.args.ct)
|
||||
fmt.Println(ca.Get(ct, "aa"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_Get(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
ct context.Context
|
||||
k K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K]
|
||||
want V
|
||||
want1 bool
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{ct, "aa"},
|
||||
want: "aaaa",
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "t2",
|
||||
m: ca,
|
||||
args: args[string]{ct, "cc"},
|
||||
want: "",
|
||||
want1: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := tt.m.Get(tt.args.ct, tt.args.k)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Get() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("Get() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_GetCache(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
c context.Context
|
||||
key K
|
||||
timeout time.Duration
|
||||
params []any
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K]
|
||||
want V
|
||||
wantErr bool
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{c: ct, key: "xx", timeout: time.Second, params: []any{ct, "xx"}},
|
||||
want: "xxxx",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.m.GetCache(tt.args.c, tt.args.key, tt.args.timeout, tt.args.params...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetCache() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetCache() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_GetCacheBatch(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
c context.Context
|
||||
key []K
|
||||
timeout time.Duration
|
||||
params []any
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K]
|
||||
want []V
|
||||
wantErr bool
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{
|
||||
c: ct,
|
||||
key: []string{"xx", "oo"},
|
||||
timeout: time.Second,
|
||||
params: []any{ct, []string{"xx", "oo"}},
|
||||
},
|
||||
want: []string{"xxxx", "oooo"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.m.GetCacheBatch(tt.args.c, tt.args.key, tt.args.timeout, tt.args.params...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetCacheBatch() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetCacheBatch() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_GetLastSetTime(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
ct context.Context
|
||||
k K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K]
|
||||
wantT time.Time
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{ct, "aa"},
|
||||
wantT: ttt,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotT := tt.m.GetLastSetTime(tt.args.ct, tt.args.k); !reflect.DeepEqual(gotT, tt.wantT) {
|
||||
t.Errorf("GetLastSetTime() = %v, want %v", gotT, tt.wantT)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_Set(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
ct context.Context
|
||||
k K
|
||||
v V
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K, V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string, string]{
|
||||
ct, "xx", "yy",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(ca.Get(ct, "xx"))
|
||||
tt.m.Set(tt.args.ct, tt.args.k, tt.args.v)
|
||||
fmt.Println(ca.Get(ct, "xx"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_SetCacheBatchFn(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
fn func(...any) (map[K]V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K, V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string, string]{batchFn},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.SetCacheBatchFn(tt.args.fn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_SetCacheFunc(t *testing.T) {
|
||||
type args[V any] struct {
|
||||
fn func(...any) (V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{fn: fn},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.SetCacheFunc(tt.args.fn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_Ttl(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
ct context.Context
|
||||
k K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K]
|
||||
want time.Duration
|
||||
}
|
||||
tx := time.Now()
|
||||
txx := ca.GetLastSetTime(ct, "aa")
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{ct, "aa"},
|
||||
want: ca.expireTime - tx.Sub(txx),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.expireTime, ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx))
|
||||
if got := tt.m.Ttl(tt.args.ct, tt.args.k); got != tt.want {
|
||||
t.Errorf("Ttl() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapCache_setCacheFn(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
fn func(...any) (map[K]V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K, V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string, string]{batchFn},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ca.cacheFunc = nil
|
||||
tt.m.setCacheFn(tt.args.fn)
|
||||
fmt.Println(ca.GetCache(ct, "xx", time.Second, ct, "xx"))
|
||||
})
|
||||
}
|
||||
}
|
101
cache/memorymapcache.go
vendored
Normal file
101
cache/memorymapcache.go
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryMapCache[K comparable, V any] struct {
|
||||
safety.Map[K, mapVal[V]]
|
||||
}
|
||||
|
||||
func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
return &MapCache[K, V]{
|
||||
data: NewMemoryMapCache[K, V](),
|
||||
cacheFunc: fn,
|
||||
expireTime: expireTime,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
r := &MapCache[K, V]{
|
||||
data: NewMemoryMapCache[K, V](),
|
||||
batchCacheFn: fn,
|
||||
expireTime: expireTime,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
r.setCacheFn(fn)
|
||||
return r
|
||||
}
|
||||
|
||||
func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] {
|
||||
return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()}
|
||||
}
|
||||
|
||||
type mapVal[T any] struct {
|
||||
setTime time.Time
|
||||
ver int
|
||||
data T
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) {
|
||||
v, ok := m.Load(key)
|
||||
if ok {
|
||||
return v.data, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Duration) {
|
||||
v, ok := m.Load(key)
|
||||
t := time.Now()
|
||||
if ok {
|
||||
v.data = val
|
||||
v.setTime = t
|
||||
v.ver++
|
||||
} else {
|
||||
v = mapVal[V]{
|
||||
setTime: t,
|
||||
ver: 1,
|
||||
data: val,
|
||||
}
|
||||
}
|
||||
m.Store(key, v)
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration {
|
||||
v, ok := m.Load(key)
|
||||
if !ok {
|
||||
return time.Duration(-1)
|
||||
}
|
||||
return expire - time.Now().Sub(v.setTime)
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int {
|
||||
v, ok := m.Load(key)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
return v.ver
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Flush(context.Context) {
|
||||
m.Map = safety.NewMap[K, mapVal[V]]()
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) {
|
||||
m.Map.Delete(key)
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context, expire time.Duration) {
|
||||
now := time.Duration(time.Now().UnixNano())
|
||||
|
||||
m.Range(func(k K, v mapVal[V]) bool {
|
||||
if now > time.Duration(v.setTime.UnixNano())+expire {
|
||||
m.Map.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
246
cache/memorymapcache_test.go
vendored
Normal file
246
cache/memorymapcache_test.go
vendored
Normal file
|
@ -0,0 +1,246 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var mm MemoryMapCache[string, string]
|
||||
var ctx context.Context
|
||||
|
||||
var ttt time.Time
|
||||
|
||||
func init() {
|
||||
ctx = context.Background()
|
||||
mm = *NewMemoryMapCache[string, string]()
|
||||
ttt = time.Now()
|
||||
mm.Store("aa", mapVal[string]{
|
||||
setTime: ttt,
|
||||
ver: 1,
|
||||
data: "bb",
|
||||
})
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
mm.Store("cc", mapVal[string]{
|
||||
setTime: time.Now(),
|
||||
ver: 1,
|
||||
data: "dd",
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_ClearExpired(t *testing.T) {
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
expire time.Duration
|
||||
}
|
||||
type testCase[K string, V string] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args{
|
||||
in0: ctx,
|
||||
expire: time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(tt.m)
|
||||
tt.m.ClearExpired(tt.args.in0, tt.args.expire)
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(tt.m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Delete(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
in0 context.Context
|
||||
key K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args[K]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args[string]{
|
||||
in0: ctx,
|
||||
key: "aa",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
tt.m.Delete(tt.args.in0, tt.args.key)
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Flush(t *testing.T) {
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args{
|
||||
in0: ctx,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Flush(tt.args.in0)
|
||||
mm.Set(ctx, "aa", "xx", time.Second)
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Get(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
in0 context.Context
|
||||
key K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args[K]
|
||||
wantR V
|
||||
wantOk bool
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args[string]{
|
||||
in0: ctx,
|
||||
key: "aa",
|
||||
},
|
||||
wantOk: true,
|
||||
wantR: "bb",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotR, gotOk := tt.m.Get(tt.args.in0, tt.args.key)
|
||||
if !reflect.DeepEqual(gotR, tt.wantR) {
|
||||
t.Errorf("Get() gotR = %v, want %v", gotR, tt.wantR)
|
||||
}
|
||||
if gotOk != tt.wantOk {
|
||||
t.Errorf("Get() gotOk = %v, want %v", gotOk, tt.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Set(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
in0 context.Context
|
||||
key K
|
||||
val V
|
||||
in3 time.Duration
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args[K, V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args[string, string]{
|
||||
in0: ctx,
|
||||
key: "ee",
|
||||
val: "ff",
|
||||
in3: time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Set(tt.args.in0, tt.args.key, tt.args.val, tt.args.in3)
|
||||
fmt.Println(tt.m.Get(ctx, tt.args.key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Ttl(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
in0 context.Context
|
||||
key K
|
||||
expire time.Duration
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args[K]
|
||||
want time.Duration
|
||||
}
|
||||
tt := time.Now()
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args[string]{key: "aa", in0: ctx, expire: time.Second * 4},
|
||||
want: 4*time.Second - tt.Sub(ttt),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.m.Ttl(tt.args.in0, tt.args.key, tt.args.expire); got != tt.want {
|
||||
t.Errorf("Ttl() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryMapCache_Ver(t *testing.T) {
|
||||
type args[K comparable] struct {
|
||||
in0 context.Context
|
||||
key K
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MemoryMapCache[K, V]
|
||||
args args[K]
|
||||
want int
|
||||
}
|
||||
mm.Set(ctx, "aa", "ff", time.Second)
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: mm,
|
||||
args: args[string]{ctx, "aa"},
|
||||
want: 2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.m.Ver(tt.args.in0, tt.args.key); got != tt.want {
|
||||
t.Errorf("Ver() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -111,7 +111,7 @@ func PostComment(c *gin.Context) {
|
|||
err = er
|
||||
return
|
||||
}
|
||||
cache.NewCommentCache().Set(up.RawQuery, string(s))
|
||||
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
|
||||
c.Redirect(http.StatusFound, res.Header.Get("Location"))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ func Detail(c *gin.Context) {
|
|||
recentComments := cache.RecentComments(c, 5)
|
||||
var ginH = gin.H{
|
||||
"title": wpconfig.Options.Value("blogname"),
|
||||
"options": wpconfig.Options,
|
||||
"recentPosts": recent,
|
||||
"archives": archive,
|
||||
"categories": categoryItems,
|
||||
|
@ -76,7 +75,7 @@ func Detail(c *gin.Context) {
|
|||
if post.PostPassword != "" && pw != post.PostPassword {
|
||||
plugins.PasswdProjectContent(&post)
|
||||
showComment = false
|
||||
} else if s, ok := cache.NewCommentCache().Get(c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) {
|
||||
} else if s, ok := cache.NewCommentCache().Get(c, c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) {
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, err = c.Writer.WriteString(s)
|
||||
|
|
|
@ -50,7 +50,7 @@ func setFeed(s string, c *gin.Context, t time.Time) {
|
|||
|
||||
func PostFeed(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(id)) {
|
||||
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(c, id)) {
|
||||
c.Status(http.StatusNotModified)
|
||||
} else {
|
||||
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id)
|
||||
|
@ -60,7 +60,7 @@ func PostFeed(c *gin.Context) {
|
|||
c.Error(err)
|
||||
return
|
||||
}
|
||||
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(id))
|
||||
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(c, id))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,6 @@ func Index(c *gin.Context) {
|
|||
recentComments := cache.RecentComments(c, 5)
|
||||
ginH := gin.H{
|
||||
"err": err,
|
||||
"options": wpconfig.Options,
|
||||
"recentPosts": recent,
|
||||
"archives": archive,
|
||||
"categories": categoryItems,
|
||||
|
|
79
internal/pkg/cache/cache.go
vendored
79
internal/pkg/cache/cache.go
vendored
|
@ -43,6 +43,8 @@ var allUsernameCache *cache.VarCache[map[string]struct{}]
|
|||
|
||||
var headerImagesCache *cache.MapCache[string, []models.PostThumbnail]
|
||||
|
||||
var ctx context.Context
|
||||
|
||||
func InitActionsCommonCache() {
|
||||
c := config.Conf.Load()
|
||||
archivesCaches = &Arch{
|
||||
|
@ -50,17 +52,17 @@ func InitActionsCommonCache() {
|
|||
setCacheFunc: dao.Archives,
|
||||
}
|
||||
|
||||
searchPostIdsCache = cache.NewMapCacheByFn[string](dao.SearchPostIds, c.SearchPostCacheTime)
|
||||
searchPostIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.SearchPostCacheTime)
|
||||
|
||||
postListIdsCache = cache.NewMapCacheByFn[string](dao.SearchPostIds, c.PostListCacheTime)
|
||||
postListIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.PostListCacheTime)
|
||||
|
||||
monthPostsCache = cache.NewMapCacheByFn[string](dao.MonthPost, c.MonthPostCacheTime)
|
||||
monthPostsCache = cache.NewMemoryMapCacheByFn[string](dao.MonthPost, c.MonthPostCacheTime)
|
||||
|
||||
postContextCache = cache.NewMapCacheByFn[uint64](dao.GetPostContext, c.ContextPostCacheTime)
|
||||
postContextCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetPostContext, c.ContextPostCacheTime)
|
||||
|
||||
postsCache = cache.NewMapCacheByBatchFn(dao.GetPostsByIds, c.PostDataCacheTime)
|
||||
postsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostsByIds, c.PostDataCacheTime)
|
||||
|
||||
postMetaCache = cache.NewMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.PostDataCacheTime)
|
||||
postMetaCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.PostDataCacheTime)
|
||||
|
||||
categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CategoryCacheTime)
|
||||
|
||||
|
@ -68,58 +70,61 @@ func InitActionsCommonCache() {
|
|||
|
||||
recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.RecentCommentsCacheTime)
|
||||
|
||||
postCommentCaches = cache.NewMapCacheByFn[uint64](dao.PostComments, c.PostCommentsCacheTime)
|
||||
postCommentCaches = cache.NewMemoryMapCacheByFn[uint64](dao.PostComments, c.PostCommentsCacheTime)
|
||||
|
||||
maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.MaxPostIdCacheTime)
|
||||
|
||||
usersCache = cache.NewMapCacheByFn[uint64](dao.GetUserById, c.UserInfoCacheTime)
|
||||
usersCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetUserById, c.UserInfoCacheTime)
|
||||
|
||||
usersNameCache = cache.NewMapCacheByFn[string](dao.GetUserByName, c.UserInfoCacheTime)
|
||||
usersNameCache = cache.NewMemoryMapCacheByFn[string](dao.GetUserByName, c.UserInfoCacheTime)
|
||||
|
||||
commentsCache = cache.NewMapCacheByBatchFn(dao.GetCommentByIds, c.CommentsCacheTime)
|
||||
commentsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetCommentByIds, c.CommentsCacheTime)
|
||||
|
||||
feedCache = cache.NewVarCache(feed, time.Hour)
|
||||
|
||||
postFeedCache = cache.NewMapCacheByFn[string](postFeed, time.Hour)
|
||||
postFeedCache = cache.NewMemoryMapCacheByFn[string](postFeed, time.Hour)
|
||||
|
||||
commentsFeedCache = cache.NewVarCache(commentsFeed, time.Hour)
|
||||
|
||||
newCommentCache = cache.NewMapCacheByFn[string, string](nil, 15*time.Minute)
|
||||
newCommentCache = cache.NewMemoryMapCacheByFn[string, string](nil, 15*time.Minute)
|
||||
|
||||
allUsernameCache = cache.NewVarCache(dao.AllUsername, c.UserInfoCacheTime)
|
||||
|
||||
headerImagesCache = cache.NewMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
|
||||
headerImagesCache = cache.NewMemoryMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
|
||||
|
||||
ctx = context.Background()
|
||||
|
||||
InitFeed()
|
||||
}
|
||||
|
||||
func ClearCache() {
|
||||
searchPostIdsCache.ClearExpired()
|
||||
postsCache.ClearExpired()
|
||||
postMetaCache.ClearExpired()
|
||||
postListIdsCache.ClearExpired()
|
||||
monthPostsCache.ClearExpired()
|
||||
postContextCache.ClearExpired()
|
||||
usersCache.ClearExpired()
|
||||
commentsCache.ClearExpired()
|
||||
usersNameCache.ClearExpired()
|
||||
postFeedCache.ClearExpired()
|
||||
newCommentCache.ClearExpired()
|
||||
headerImagesCache.ClearExpired()
|
||||
searchPostIdsCache.ClearExpired(ctx)
|
||||
searchPostIdsCache.ClearExpired(ctx)
|
||||
postsCache.ClearExpired(ctx)
|
||||
postMetaCache.ClearExpired(ctx)
|
||||
postListIdsCache.ClearExpired(ctx)
|
||||
monthPostsCache.ClearExpired(ctx)
|
||||
postContextCache.ClearExpired(ctx)
|
||||
usersCache.ClearExpired(ctx)
|
||||
commentsCache.ClearExpired(ctx)
|
||||
usersNameCache.ClearExpired(ctx)
|
||||
postFeedCache.ClearExpired(ctx)
|
||||
newCommentCache.ClearExpired(ctx)
|
||||
headerImagesCache.ClearExpired(ctx)
|
||||
}
|
||||
func FlushCache() {
|
||||
searchPostIdsCache.Flush()
|
||||
postsCache.Flush()
|
||||
postMetaCache.Flush()
|
||||
postListIdsCache.Flush()
|
||||
monthPostsCache.Flush()
|
||||
postContextCache.Flush()
|
||||
usersCache.Flush()
|
||||
commentsCache.Flush()
|
||||
usersCache.Flush()
|
||||
postFeedCache.Flush()
|
||||
newCommentCache.Flush()
|
||||
headerImagesCache.Flush()
|
||||
searchPostIdsCache.Flush(ctx)
|
||||
postsCache.Flush(ctx)
|
||||
postMetaCache.Flush(ctx)
|
||||
postListIdsCache.Flush(ctx)
|
||||
monthPostsCache.Flush(ctx)
|
||||
postContextCache.Flush(ctx)
|
||||
usersCache.Flush(ctx)
|
||||
commentsCache.Flush(ctx)
|
||||
usersCache.Flush(ctx)
|
||||
postFeedCache.Flush(ctx)
|
||||
newCommentCache.Flush(ctx)
|
||||
headerImagesCache.Flush(ctx)
|
||||
}
|
||||
|
||||
func Archives(ctx context.Context) (r []models.PostArchive) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/config"
|
||||
|
@ -12,16 +13,18 @@ import (
|
|||
)
|
||||
|
||||
var digestCache *cache.MapCache[uint64, string]
|
||||
var ctx context.Context
|
||||
|
||||
func InitDigestCache() {
|
||||
digestCache = cache.NewMapCacheByFn[uint64](digestRaw, config.Conf.Load().DigestCacheTime)
|
||||
ctx = context.Background()
|
||||
digestCache = cache.NewMemoryMapCacheByFn[uint64](digestRaw, config.Conf.Load().DigestCacheTime)
|
||||
}
|
||||
|
||||
func ClearDigestCache() {
|
||||
digestCache.ClearExpired()
|
||||
digestCache.ClearExpired(ctx)
|
||||
}
|
||||
func FlushCache() {
|
||||
digestCache.Flush()
|
||||
digestCache.Flush(ctx)
|
||||
}
|
||||
|
||||
func digestRaw(arg ...any) (string, error) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Finds can use offset
|
||||
// Finds 比 Find 多一个offset
|
||||
//
|
||||
// Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数
|
||||
func Finds[T Model](ctx context.Context, q *QueryCondition) (r []T, err error) {
|
||||
|
@ -132,3 +132,10 @@ func Chunk[T Model, R any](ctx context.Context, perLimit int, fn func(rows T) (R
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pagination 同 SimplePagination
|
||||
//
|
||||
// Condition 中可使用 Where Fields Group Having Join Order Page Limit In 函数
|
||||
func Pagination[T Model](ctx context.Context, q *QueryCondition) ([]T, int, error) {
|
||||
return SimplePagination[T](ctx, q.where, q.fields, q.group, q.page, q.limit, q.order, q.join, q.having, q.in...)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package model
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"reflect"
|
||||
|
@ -173,3 +174,59 @@ func TestChunk(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagination(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
q *QueryCondition
|
||||
}
|
||||
type testCase[T Model] struct {
|
||||
name string
|
||||
args args
|
||||
want []T
|
||||
want1 int
|
||||
wantErr bool
|
||||
}
|
||||
tests := []testCase[post]{
|
||||
{
|
||||
name: "t1",
|
||||
args: args{
|
||||
ctx: ctx,
|
||||
q: Conditions(
|
||||
Where(SqlBuilder{
|
||||
{"ID", "in", ""},
|
||||
}),
|
||||
Page(1),
|
||||
Limit(5),
|
||||
In([][]any{slice.ToAnySlice(number.Range(431, 440, 1))}...),
|
||||
),
|
||||
},
|
||||
want: func() (r []post) {
|
||||
r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?,?,?,?)", slice.ToAnySlice(number.Range(431, 435, 1))...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
panic(err)
|
||||
} else if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}(),
|
||||
want1: 10,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, err := Pagination[post](tt.args.ctx, tt.args.q)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Pagination() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Pagination() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("Pagination() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user