feat: add middleware.cache

This commit is contained in:
loveuer 2024-07-25 17:31:57 +08:00
parent cee0bfb43d
commit 53f124c9ec
5 changed files with 159 additions and 4 deletions

4
go.mod
View File

@ -8,11 +8,12 @@ require (
github.com/glebarez/sqlite v1.10.0 github.com/glebarez/sqlite v1.10.0
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jackc/pgtype v1.12.0 github.com/jackc/pgtype v1.12.0
github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/jedib0t/go-pretty/v6 v6.5.9
github.com/loveuer/esgo2dump v0.3.3 github.com/loveuer/esgo2dump v0.3.3
github.com/loveuer/nf v0.2.7 github.com/loveuer/nf v0.2.8
github.com/loveuer/ngorm/v2 v2.1.1 github.com/loveuer/ngorm/v2 v2.1.1
github.com/rabbitmq/amqp091-go v1.10.0 github.com/rabbitmq/amqp091-go v1.10.0
github.com/samber/lo v1.39.0 github.com/samber/lo v1.39.0
@ -35,7 +36,6 @@ require (
github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect github.com/jackc/pgconn v1.13.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgio v1.0.0 // indirect

4
go.sum
View File

@ -146,8 +146,8 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/loveuer/esgo2dump v0.3.3 h1:/AidoaFV7bDRyT1ycyBKs4XGmyVs2ShaUKrpEBiUWkM= github.com/loveuer/esgo2dump v0.3.3 h1:/AidoaFV7bDRyT1ycyBKs4XGmyVs2ShaUKrpEBiUWkM=
github.com/loveuer/esgo2dump v0.3.3/go.mod h1:thZvfsO0kd7Ck3TA0jc9rRc4CuIa4Iuiq6tF3tCqXEY= github.com/loveuer/esgo2dump v0.3.3/go.mod h1:thZvfsO0kd7Ck3TA0jc9rRc4CuIa4Iuiq6tF3tCqXEY=
github.com/loveuer/nf v0.2.7 h1:p17Y2yvO6u1qnSvYawQsMzBb2Xw7EzkV87r6BZzhdfA= github.com/loveuer/nf v0.2.8 h1:Qo0M748TglS6E5geh1LG0IBkrjLm+5yUs3II9l50tEQ=
github.com/loveuer/nf v0.2.7/go.mod h1:M6reF17/kJBis30H4DxR5hrtgo/oJL4AV4cBe4HzJLw= github.com/loveuer/nf v0.2.8/go.mod h1:M6reF17/kJBis30H4DxR5hrtgo/oJL4AV4cBe4HzJLw=
github.com/loveuer/ngorm/v2 v2.1.1 h1:v+ut5BjeSBFU87o800pI8Q3fXEOUAkvk5+btMw2oOEc= github.com/loveuer/ngorm/v2 v2.1.1 h1:v+ut5BjeSBFU87o800pI8Q3fXEOUAkvk5+btMw2oOEc=
github.com/loveuer/ngorm/v2 v2.1.1/go.mod h1:BVhFGQsRMdcf08MtmwwRihwCR/x7wDd0Fzy8Xj+edM0= github.com/loveuer/ngorm/v2 v2.1.1/go.mod h1:BVhFGQsRMdcf08MtmwwRihwCR/x7wDd0Fzy8Xj+edM0=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=

View File

@ -23,6 +23,11 @@ func handleValue(value any) ([]byte, error) {
err error err error
) )
switch value.(type) {
case []byte:
return value.([]byte), nil
}
if imp, ok := value.(encoded_value); ok { if imp, ok := value.(encoded_value); ok {
bs, err = imp.MarshalBinary() bs, err = imp.MarshalBinary()
} else { } else {

129
internal/middleware/cache/cache.go vendored Normal file
View File

@ -0,0 +1,129 @@
package cache
import (
"bytes"
"encoding/json"
"errors"
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/log"
"io"
"net/http"
"strconv"
"time"
"ultone/internal/database/cache"
"ultone/internal/model"
)
var (
defaultKeyFn = func(c *nf.Ctx) string {
return c.Request.URL.String()
}
defaultTimeout = 3600
defaultPrefix = "midd:cache"
)
type Config struct {
// if return "" (won't cache)
KeyFn func(c *nf.Ctx) string
// cache timeout(seconds)
Timeout int
Prefix string
Refresh bool
}
type store struct {
Body []byte `json:"body"`
Header http.Header `json:"header"`
When int64 `json:"when"`
}
func New(cfgs ...Config) nf.HandlerFunc {
if cache.Client == nil {
log.Panic("[middleware.cache] database cache client is nil")
}
var cfg Config
if len(cfgs) > 0 {
cfg = cfgs[0]
}
if cfg.KeyFn == nil {
cfg.KeyFn = defaultKeyFn
}
if cfg.Timeout <= 0 {
cfg.Timeout = defaultTimeout
}
if cfg.Prefix == "" {
cfg.Prefix = defaultPrefix
}
return func(c *nf.Ctx) error {
var (
key string
err error
bs []byte
res = new(store)
)
if key = cfg.KeyFn(c); key == "" {
return c.Next()
}
key = cfg.Prefix + ":" + key
duration := time.Duration(cfg.Timeout) * time.Second
if cfg.Refresh {
if bs, err = cache.Client.GetEx(c.Context(), key, duration); err != nil {
if !errors.Is(err, cache.ErrorKeyNotFound) {
log.Warn("[middleware.cache] cache get err: %s", err.Error())
}
goto FromNext
}
} else {
if bs, err = cache.Client.Get(c.Context(), key); err != nil {
if !errors.Is(err, cache.ErrorKeyNotFound) {
log.Warn("[middleware.cache] cache get err: %s", err.Error())
}
goto FromNext
}
}
if err = json.Unmarshal(bs, res); err != nil {
log.Warn("[middleware.cache] cache data unamrshal err: %s", err.Error())
goto FromNext
}
for key := range res.Header {
for idx := range res.Header[key] {
c.SetHeader(key, res.Header[key][idx])
}
}
c.SetHeader("X-Nf-Cache-At", strconv.Itoa(int(res.When)))
_, err = c.Write(res.Body)
return err
FromNext:
blw := &model.CustomResponseWriter{Body: bytes.NewBuffer(make([]byte, 0, 256)), ResponseWriter: c.Writer}
c.Writer = blw
rerr := c.Next()
resp, _ := io.ReadAll(blw.Body)
data := &store{Body: resp, Header: blw.Header().Clone(), When: time.Now().UnixMilli()}
cbs, _ := json.Marshal(data)
if err = cache.Client.SetEx(c.Context(), key, cbs, duration); err != nil {
log.Warn("[middleware.cache] cache client setex err: %s", err.Error())
}
return rerr
}
}

21
internal/model/writer.go Normal file
View File

@ -0,0 +1,21 @@
package model
import (
"bytes"
"github.com/loveuer/nf"
)
type CustomResponseWriter struct {
nf.ResponseWriter
Body *bytes.Buffer
}
func (w CustomResponseWriter) Write(b []byte) (int, error) {
w.Body.Write(b)
return w.ResponseWriter.Write(b)
}
func (w CustomResponseWriter) WriteString(s string) (int, error) {
w.Body.WriteString(s)
return w.ResponseWriter.WriteString(s)
}