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"
|
"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
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
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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 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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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...)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user