add pagination cache
This commit is contained in:
parent
74304b5b12
commit
227de8bdc8
|
@ -76,6 +76,7 @@ func SetupRouter() *gin.Engine {
|
||||||
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
|
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
|
||||||
r.POST("/login", actions.Login)
|
r.POST("/login", actions.Login)
|
||||||
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
|
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
|
||||||
|
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
|
||||||
r.GET("/p/:id/feed", actions.PostFeed)
|
r.GET("/p/:id/feed", actions.PostFeed)
|
||||||
r.GET("/feed", actions.Feed)
|
r.GET("/feed", actions.Feed)
|
||||||
r.GET("/comments/feed", actions.CommentsFeed)
|
r.GET("/comments/feed", actions.CommentsFeed)
|
||||||
|
|
25
cache/cachemanager/manger.go
vendored
25
cache/cachemanager/manger.go
vendored
|
@ -11,8 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx = context.Background()
|
|
||||||
|
|
||||||
var mapFlush = safety.NewMap[string, func(any)]()
|
var mapFlush = safety.NewMap[string, func(any)]()
|
||||||
var anyFlush = safety.NewMap[string, func()]()
|
var anyFlush = safety.NewMap[string, func()]()
|
||||||
|
|
||||||
|
@ -60,6 +58,7 @@ var clears []clearExpired
|
||||||
var flushes []flush
|
var flushes []flush
|
||||||
|
|
||||||
func Flush() {
|
func Flush() {
|
||||||
|
ctx := context.WithValue(context.Background(), "execFlushBy", "mangerFlushFn")
|
||||||
for _, f := range flushes {
|
for _, f := range flushes {
|
||||||
f.Flush(ctx)
|
f.Flush(ctx)
|
||||||
}
|
}
|
||||||
|
@ -88,11 +87,13 @@ func PushMangerMap[K comparable, V any](name string, m *cache.MapCache[K, V]) {
|
||||||
}
|
}
|
||||||
mapCache.Store(name, m)
|
mapCache.Store(name, m)
|
||||||
anyFlush.Store(name, func() {
|
anyFlush.Store(name, func() {
|
||||||
|
ctx := context.WithValue(context.Background(), "ctx", "registerFlush")
|
||||||
m.Flush(ctx)
|
m.Flush(ctx)
|
||||||
})
|
})
|
||||||
mapFlush.Store(name, func(a any) {
|
mapFlush.Store(name, func(a any) {
|
||||||
k, ok := a.([]K)
|
k, ok := a.([]K)
|
||||||
if ok && len(k) > 0 {
|
if ok && len(k) > 0 {
|
||||||
|
ctx := context.WithValue(context.Background(), "ctx", "registerFlush")
|
||||||
m.Del(ctx, k...)
|
m.Del(ctx, k...)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -168,6 +169,25 @@ func parseArgs(args ...any) (string, func() time.Duration) {
|
||||||
return name, fn
|
return name, fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPaginationCache[K comparable, V any](m *cache.MapCache[string, helper.PaginationData[V]], maxNum int,
|
||||||
|
dbFn cache.DbFn[K, V], localFn cache.LocalFn[K, V], dbKeyFn func(K, ...any) string, fetchNum int, name string, a ...any) *cache.Pagination[K, V] {
|
||||||
|
fn := helper.ParseArgs([]func() int(nil), a...)
|
||||||
|
var ma, fet func() int
|
||||||
|
if len(fn) > 0 {
|
||||||
|
ma = fn[0]
|
||||||
|
if len(fn) > 1 {
|
||||||
|
fet = fn[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ma == nil {
|
||||||
|
ma = reload.FnVal(str.Join("paginationCache-", name, "-maxNum"), maxNum, nil)
|
||||||
|
}
|
||||||
|
if fet == nil {
|
||||||
|
fet = reload.FnVal(str.Join("paginationCache-", name, "-fetchNum"), fetchNum, nil)
|
||||||
|
}
|
||||||
|
return cache.NewPagination(m, ma, dbFn, localFn, dbKeyFn, fet, name)
|
||||||
|
}
|
||||||
|
|
||||||
func NewMapCache[K comparable, V any](data cache.Cache[K, V], batchFn cache.MapBatchFn[K, V], fn cache.MapSingleFn[K, V], args ...any) *cache.MapCache[K, V] {
|
func NewMapCache[K comparable, V any](data cache.Cache[K, V], batchFn cache.MapBatchFn[K, V], fn cache.MapSingleFn[K, V], args ...any) *cache.MapCache[K, V] {
|
||||||
inc := helper.ParseArgs((*cache.IncreaseUpdate[K, V])(nil), args...)
|
inc := helper.ParseArgs((*cache.IncreaseUpdate[K, V])(nil), args...)
|
||||||
m := cache.NewMapCache[K, V](data, fn, batchFn, inc)
|
m := cache.NewMapCache[K, V](data, fn, batchFn, inc)
|
||||||
|
@ -213,6 +233,7 @@ func ClearPush(c ...clearExpired) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearExpired() {
|
func ClearExpired() {
|
||||||
|
ctx := context.WithValue(context.Background(), "execClearExpired", "mangerClearExpiredFn")
|
||||||
for _, c := range clears {
|
for _, c := range clears {
|
||||||
c.ClearExpired(ctx)
|
c.ClearExpired(ctx)
|
||||||
}
|
}
|
||||||
|
|
2
cache/cachemanager/manger_test.go
vendored
2
cache/cachemanager/manger_test.go
vendored
|
@ -13,6 +13,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
func TestFlushMapVal(t *testing.T) {
|
func TestFlushMapVal(t *testing.T) {
|
||||||
_ = number.Range(1, 5, 0)
|
_ = number.Range(1, 5, 0)
|
||||||
t.Run("t1", func(t *testing.T) {
|
t.Run("t1", func(t *testing.T) {
|
||||||
|
|
145
cache/pagination.go
vendored
Normal file
145
cache/pagination.go
vendored
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fthvgb1/wp-go/helper"
|
||||||
|
"github.com/fthvgb1/wp-go/helper/number"
|
||||||
|
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||||
|
"github.com/fthvgb1/wp-go/safety"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pagination[K comparable, V any] struct {
|
||||||
|
*MapCache[string, helper.PaginationData[V]]
|
||||||
|
maxNum func() int
|
||||||
|
isSwitch *safety.Map[K, bool]
|
||||||
|
dbFn func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
|
||||||
|
localFn func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
|
||||||
|
batchFetchNum func() int
|
||||||
|
dbKeyFn func(K K, a ...any) string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var switchDb = errors.New("switch Db")
|
||||||
|
|
||||||
|
type DbFn[K comparable, V any] func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
|
||||||
|
|
||||||
|
type LocalFn[K comparable, V any] func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
|
||||||
|
|
||||||
|
func NewPagination[K comparable, V any](m *MapCache[string, helper.PaginationData[V]], maxNum func() int, dbFn DbFn[K, V], localFn LocalFn[K, V], dbKeyFn func(K, ...any) string, batchFetchNum func() int, name string) *Pagination[K, V] {
|
||||||
|
if dbKeyFn == nil {
|
||||||
|
dbKeyFn = func(k K, a ...any) string {
|
||||||
|
s := str.NewBuilder()
|
||||||
|
for _, v := range append([]any{k}, a...) {
|
||||||
|
s.Sprintf("%v|", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimRight(s.String(), "|")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Pagination[K, V]{
|
||||||
|
MapCache: m,
|
||||||
|
maxNum: maxNum,
|
||||||
|
isSwitch: safety.NewMap[K, bool](),
|
||||||
|
dbFn: dbFn,
|
||||||
|
localFn: localFn,
|
||||||
|
batchFetchNum: batchFetchNum,
|
||||||
|
name: name,
|
||||||
|
dbKeyFn: dbKeyFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pagination[K, V]) Pagination(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
|
||||||
|
if is, _ := p.isSwitch.Load(k); is {
|
||||||
|
return p.paginationByDB(ctx, timeout, k, page, limit, 0, a...)
|
||||||
|
}
|
||||||
|
data, total, err := p.paginationByLocal(ctx, timeout, k, page, limit, a...)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, switchDb) {
|
||||||
|
err = nil
|
||||||
|
return p.paginationByDB(ctx, timeout, k, page, limit, total, a...)
|
||||||
|
}
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return data, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pagination[K, V]) paginationByLocal(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
|
||||||
|
key := fmt.Sprintf("%v", k)
|
||||||
|
data, ok := p.Get(ctx, key)
|
||||||
|
if ok {
|
||||||
|
if p.increaseUpdate != nil && p.refresh != nil {
|
||||||
|
dat, err := p.increaseUpdates(ctx, timeout, data, key, a...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if dat.TotalRaw >= p.maxNum() {
|
||||||
|
return nil, 0, switchDb
|
||||||
|
}
|
||||||
|
data = dat
|
||||||
|
}
|
||||||
|
return p.localFn(ctx, data.Data, k, page, limit, a...)
|
||||||
|
}
|
||||||
|
batchNum := p.batchFetchNum()
|
||||||
|
da, totalRaw, err := p.fetchDb(ctx, timeout, k, 1, 0, 0, a...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if totalRaw >= p.maxNum() {
|
||||||
|
p.isSwitch.Store(k, true)
|
||||||
|
return nil, totalRaw, switchDb
|
||||||
|
}
|
||||||
|
if len(da) < totalRaw {
|
||||||
|
totalPage := number.DivideCeil(totalRaw, batchNum)
|
||||||
|
for i := 1; i <= totalPage; i++ {
|
||||||
|
daa, _, err := p.fetchDb(ctx, timeout, k, i, batchNum, totalRaw, a...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
da = append(da, daa...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.localFn(ctx, data.Data, k, page, limit, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pagination[K, V]) paginationByDB(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
|
||||||
|
key := p.dbKeyFn(k, append([]any{page, limit}, a...)...)
|
||||||
|
data, ok := p.Get(ctx, key)
|
||||||
|
if ok {
|
||||||
|
return data.Data, data.TotalRaw, nil
|
||||||
|
}
|
||||||
|
dat, total, err := p.fetchDb(ctx, timeout, k, page, limit, totalRaw, a...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
data.Data, data.TotalRaw = dat, total
|
||||||
|
p.Set(ctx, key, data)
|
||||||
|
return data.Data, data.TotalRaw, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pagination[K, V]) fetchDb(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
|
||||||
|
var data helper.PaginationData[V]
|
||||||
|
var err error
|
||||||
|
fn := func() {
|
||||||
|
da, total, er := p.dbFn(ctx, k, page, limit, totalRaw, a...)
|
||||||
|
if er != nil {
|
||||||
|
err = er
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.Data = da
|
||||||
|
data.TotalRaw = total
|
||||||
|
}
|
||||||
|
if timeout > 0 {
|
||||||
|
er := helper.RunFnWithTimeout(ctx, timeout, fn, fmt.Sprintf("fetch %s-[%v]-page[%d]-limit[%d] from db fail", p.name, k, page, limit))
|
||||||
|
if err == nil && er != nil {
|
||||||
|
err = er
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
return data.Data, data.TotalRaw, err
|
||||||
|
}
|
6
helper/others.go
Normal file
6
helper/others.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package helper
|
||||||
|
|
||||||
|
type PaginationData[T any] struct {
|
||||||
|
Data []T
|
||||||
|
TotalRaw int
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ func (c count[T]) Table() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func pagination[T Model](db dbQuery, ctx context.Context, q *QueryCondition, page, pageSize int) (r []T, total int, err error) {
|
func pagination[T Model](db dbQuery, ctx context.Context, q *QueryCondition, page, pageSize int) (r []T, total int, err error) {
|
||||||
if page < 1 || pageSize < 1 {
|
if page < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q.Limit = pageSize
|
q.Limit = pageSize
|
||||||
|
@ -65,7 +65,9 @@ func pagination[T Model](db dbQuery, ctx context.Context, q *QueryCondition, pag
|
||||||
} else {
|
} else {
|
||||||
total = q.TotalRow
|
total = q.TotalRow
|
||||||
}
|
}
|
||||||
|
if q.Limit <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
offset := 0
|
offset := 0
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
offset = (page - 1) * q.Limit
|
offset = (page - 1) * q.Limit
|
||||||
|
|
Loading…
Reference in New Issue
Block a user