Merge pull request #10 from fthvgb1/dev

Dev
This commit is contained in:
2023-02-03 14:34:42 +08:00 committed by GitHub
commit 504b0efd66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 914 additions and 178 deletions

16
cache/interface.go vendored Normal file
View 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
View File

@ -4,42 +4,36 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/helper/slice"
"sync" "sync"
"time" "time"
) )
type MapCache[K comparable, V any] struct { type MapCache[K comparable, V any] struct {
data safety.Map[K, mapCacheStruct[V]] data Cache[K, V]
mutex *sync.Mutex mux sync.Mutex
cacheFunc func(...any) (V, error) cacheFunc func(...any) (V, error)
batchCacheFn func(...any) (map[K]V, error) batchCacheFn func(...any) (map[K]V, error)
expireTime time.Duration 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)) { func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) {
m.cacheFunc = fn m.cacheFunc = fn
} }
func (m *MapCache[K, V]) GetLastSetTime(k K) (t time.Time) { func (m *MapCache[K, V]) Ttl(ctx context.Context, k K) time.Duration {
r, ok := m.data.Load(k) return m.data.Ttl(ctx, k, m.expireTime)
if ok {
t = r.setTime
}
return
} }
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 m.batchCacheFn = fn
if m.cacheFunc == nil { if m.cacheFunc == nil {
m.setCacheFn(fn) 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]{ return &MapCache[K, V]{
mutex: &sync.Mutex{}, mux: sync.Mutex{},
cacheFunc: fn, cacheFunc: fn,
expireTime: expireTime, 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]{ r := &MapCache[K, V]{
mutex: &sync.Mutex{}, mux: sync.Mutex{},
batchCacheFn: fn, batchCacheFn: fn,
expireTime: expireTime, expireTime: expireTime,
data: safety.NewMap[K, mapCacheStruct[V]](), data: cacheType,
} }
r.setCacheFn(fn) r.setCacheFn(fn)
return r return r
} }
func (m *MapCache[K, V]) Flush() { func (m *MapCache[K, V]) Flush(ctx context.Context) {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
m.data = safety.NewMap[K, mapCacheStruct[V]]() m.data.Flush(ctx)
} }
func (m *MapCache[K, V]) Get(k K) (V, bool) { func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) {
r, ok := m.data.Load(k) return m.data.Get(ctx, k)
if ok {
return r.data, ok
}
var rr V
return rr, false
} }
func (m *MapCache[K, V]) Set(k K, v V) { func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) {
m.set(k, v) m.data.Set(ctx, k, v, m.expireTime)
}
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]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) { func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) {
data, ok := m.data.Load(key) data, ok := m.data.Get(c, key)
if !ok {
data = mapCacheStruct[V]{}
}
now := time.Duration(time.Now().UnixNano())
var err error var err error
expired := time.Duration(data.setTime.UnixNano())+m.expireTime < now if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 {
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢? ver := m.data.Ver(c, key)
if !ok || (ok && m.expireTime >= 0 && expired) {
t := data.incr
call := func() { call := func() {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
da, ok := m.data.Load(key) if m.data.Ver(c, key) > ver {
if ok && da.incr > t { data, _ = m.data.Get(c, key)
return return
} else {
da = data
} }
r, er := m.cacheFunc(params...) data, err = m.cacheFunc(params...)
if err != nil { if err != nil {
err = er
return return
} }
m.set(key, r) m.Set(c, key, data)
data.data = r
} }
if timeout > 0 { if timeout > 0 {
ctx, cancel := context.WithTimeout(c, timeout) 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) { func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
var needFlush []K
var res []V var res []V
t := 0 ver := 0
now := time.Duration(time.Now().UnixNano()) needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
for _, k := range key { if _, ok := m.data.Get(c, k); !ok {
d, ok := m.data.Load(k) return k, true
if !ok {
needFlush = append(needFlush, k)
continue
} }
expired := time.Duration(d.setTime.UnixNano())+m.expireTime < now ver += m.data.Ver(c, k)
if expired { return
needFlush = append(needFlush, k) })
}
t = t + d.incr
}
var err error var err error
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢?
if len(needFlush) > 0 { if len(needFlush) > 0 {
call := func() { call := func() {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
tt := 0
for _, dd := range needFlush { vers := slice.Reduce(needFlush, func(t K, r int) int {
if ddd, ok := m.data.Load(dd); ok { r += m.data.Ver(c, t)
tt = tt + ddd.incr return r
} }, 0)
}
if tt > t { if vers > ver {
return return
} }
r, er := m.batchCacheFn(params...) r, er := m.batchCacheFn(params...)
if err != nil { if err != nil {
err = er err = er
return return
} }
for k, v := range r { for k, v := range r {
m.set(k, v) m.Set(c, k, v)
} }
} }
if timeout > 0 { if timeout > 0 {
@ -244,23 +186,14 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.
call() call()
} }
} }
for _, k := range key { res = slice.FilterAndMap(key, func(k K) (V, bool) {
d, ok := m.data.Load(k) return m.data.Get(c, k)
if ok { })
res = append(res, d.data)
}
}
return res, err return res, err
} }
func (m *MapCache[K, V]) ClearExpired() { func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
now := time.Duration(time.Now().UnixNano()) m.mux.Lock()
m.mutex.Lock() defer m.mux.Unlock()
defer m.mutex.Unlock() m.data.ClearExpired(ctx, m.expireTime)
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
})
} }

370
cache/map_test.go vendored Normal file
View 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
View 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
View 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)
}
})
}
}

View File

@ -111,7 +111,7 @@ func PostComment(c *gin.Context) {
err = er err = er
return return
} }
cache.NewCommentCache().Set(up.RawQuery, string(s)) cache.NewCommentCache().Set(c, up.RawQuery, string(s))
c.Redirect(http.StatusFound, res.Header.Get("Location")) c.Redirect(http.StatusFound, res.Header.Get("Location"))
return return
} }

View File

@ -25,7 +25,6 @@ func Detail(c *gin.Context) {
recentComments := cache.RecentComments(c, 5) recentComments := cache.RecentComments(c, 5)
var ginH = gin.H{ var ginH = gin.H{
"title": wpconfig.Options.Value("blogname"), "title": wpconfig.Options.Value("blogname"),
"options": wpconfig.Options,
"recentPosts": recent, "recentPosts": recent,
"archives": archive, "archives": archive,
"categories": categoryItems, "categories": categoryItems,
@ -76,7 +75,7 @@ func Detail(c *gin.Context) {
if post.PostPassword != "" && pw != post.PostPassword { if post.PostPassword != "" && pw != post.PostPassword {
plugins.PasswdProjectContent(&post) plugins.PasswdProjectContent(&post)
showComment = false 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.WriteHeader(http.StatusOK)
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
_, err = c.Writer.WriteString(s) _, err = c.Writer.WriteString(s)

View File

@ -50,7 +50,7 @@ func setFeed(s string, c *gin.Context, t time.Time) {
func PostFeed(c *gin.Context) { func PostFeed(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(id)) { if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified) c.Status(http.StatusNotModified)
} else { } else {
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id) s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id)
@ -60,7 +60,7 @@ func PostFeed(c *gin.Context) {
c.Error(err) c.Error(err)
return return
} }
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(id)) setFeed(s, c, cache.PostFeedCache().GetLastSetTime(c, id))
} }
} }

View File

@ -241,7 +241,6 @@ func Index(c *gin.Context) {
recentComments := cache.RecentComments(c, 5) recentComments := cache.RecentComments(c, 5)
ginH := gin.H{ ginH := gin.H{
"err": err, "err": err,
"options": wpconfig.Options,
"recentPosts": recent, "recentPosts": recent,
"archives": archive, "archives": archive,
"categories": categoryItems, "categories": categoryItems,

View File

@ -43,6 +43,8 @@ var allUsernameCache *cache.VarCache[map[string]struct{}]
var headerImagesCache *cache.MapCache[string, []models.PostThumbnail] var headerImagesCache *cache.MapCache[string, []models.PostThumbnail]
var ctx context.Context
func InitActionsCommonCache() { func InitActionsCommonCache() {
c := config.Conf.Load() c := config.Conf.Load()
archivesCaches = &Arch{ archivesCaches = &Arch{
@ -50,17 +52,17 @@ func InitActionsCommonCache() {
setCacheFunc: dao.Archives, 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) categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CategoryCacheTime)
@ -68,58 +70,61 @@ func InitActionsCommonCache() {
recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.RecentCommentsCacheTime) 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) 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) 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) 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) 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() InitFeed()
} }
func ClearCache() { func ClearCache() {
searchPostIdsCache.ClearExpired() searchPostIdsCache.ClearExpired(ctx)
postsCache.ClearExpired() searchPostIdsCache.ClearExpired(ctx)
postMetaCache.ClearExpired() postsCache.ClearExpired(ctx)
postListIdsCache.ClearExpired() postMetaCache.ClearExpired(ctx)
monthPostsCache.ClearExpired() postListIdsCache.ClearExpired(ctx)
postContextCache.ClearExpired() monthPostsCache.ClearExpired(ctx)
usersCache.ClearExpired() postContextCache.ClearExpired(ctx)
commentsCache.ClearExpired() usersCache.ClearExpired(ctx)
usersNameCache.ClearExpired() commentsCache.ClearExpired(ctx)
postFeedCache.ClearExpired() usersNameCache.ClearExpired(ctx)
newCommentCache.ClearExpired() postFeedCache.ClearExpired(ctx)
headerImagesCache.ClearExpired() newCommentCache.ClearExpired(ctx)
headerImagesCache.ClearExpired(ctx)
} }
func FlushCache() { func FlushCache() {
searchPostIdsCache.Flush() searchPostIdsCache.Flush(ctx)
postsCache.Flush() postsCache.Flush(ctx)
postMetaCache.Flush() postMetaCache.Flush(ctx)
postListIdsCache.Flush() postListIdsCache.Flush(ctx)
monthPostsCache.Flush() monthPostsCache.Flush(ctx)
postContextCache.Flush() postContextCache.Flush(ctx)
usersCache.Flush() usersCache.Flush(ctx)
commentsCache.Flush() commentsCache.Flush(ctx)
usersCache.Flush() usersCache.Flush(ctx)
postFeedCache.Flush() postFeedCache.Flush(ctx)
newCommentCache.Flush() newCommentCache.Flush(ctx)
headerImagesCache.Flush() headerImagesCache.Flush(ctx)
} }
func Archives(ctx context.Context) (r []models.PostArchive) { func Archives(ctx context.Context) (r []models.PostArchive) {

View File

@ -1,6 +1,7 @@
package plugins package plugins
import ( import (
"context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
@ -12,16 +13,18 @@ import (
) )
var digestCache *cache.MapCache[uint64, string] var digestCache *cache.MapCache[uint64, string]
var ctx context.Context
func InitDigestCache() { 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() { func ClearDigestCache() {
digestCache.ClearExpired() digestCache.ClearExpired(ctx)
} }
func FlushCache() { func FlushCache() {
digestCache.Flush() digestCache.Flush(ctx)
} }
func digestRaw(arg ...any) (string, error) { func digestRaw(arg ...any) (string, error) {

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
// Finds can use offset // Finds 比 Find 多一个offset
// //
// Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数 // Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数
func Finds[T Model](ctx context.Context, q *QueryCondition) (r []T, err error) { 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 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...)
}

View File

@ -2,6 +2,7 @@ package model
import ( import (
"context" "context"
"database/sql"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"reflect" "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)
}
})
}
}