Compare commits
1
Commits
97b26635fa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c812cfb663 |
+17
@@ -2,6 +2,7 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json/v2"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -66,3 +67,19 @@ func (c *Cache) Set[T any](ctx context.Context, entry Entry[T]) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenCacheKey(table, sql string, args ...any) (key string, err error) {
|
||||
prefix := fmt.Sprintf("lyra:cache:%s:", table)
|
||||
h := md5.New()
|
||||
// 写入查询语句
|
||||
h.Write([]byte(sql))
|
||||
// 序列化并写入每个参数
|
||||
for _, arg := range args {
|
||||
jsonData, err := json.Marshal(arg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("序列化参数失败:%w", err)
|
||||
}
|
||||
h.Write(jsonData)
|
||||
}
|
||||
return prefix + fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"lyra/internal/cache"
|
||||
"lyra/internal/pkg/constant"
|
||||
"lyra/internal/pkg/query"
|
||||
"lyra/internal/util"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -18,18 +19,22 @@ type Category struct {
|
||||
Slug string `db:"slug"`
|
||||
Title string `db:"title"`
|
||||
Sort int `db:"sort"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
type CategoryModel interface {
|
||||
FindAll(ctx context.Context) ([]*Category, error)
|
||||
ListWithOptions(ctx context.Context, queryOpt query.Option, modelOpts ...ModelOption) (resp []*Link, err error)
|
||||
}
|
||||
type defaultCategoryModel struct {
|
||||
db *sqlx.DB
|
||||
table string
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewCategoryModel(db *sqlx.DB, cache *cache.Cache) CategoryModel {
|
||||
return &defaultCategoryModel{db: db, cache: cache}
|
||||
return &defaultCategoryModel{db: db, table: "categories", cache: cache}
|
||||
}
|
||||
|
||||
const categoryListCacheTTL = 5 * time.Minute
|
||||
@@ -68,3 +73,60 @@ func (m *defaultCategoryModel) FindAll(ctx context.Context) ([]*Category, error)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
type ModelOption func(*sq.SelectBuilder)
|
||||
|
||||
func (m *defaultCategoryModel) ListWithOptions(ctx context.Context, queryOpt query.Option, modelOpts ...ModelOption) (resp []*Link, err error) {
|
||||
// 1.先构建查询语句(还没有deleted字段)
|
||||
// 2.根据queryOpt的参数,设置分页、排序等
|
||||
sqBuilder := sq.Select("id", "slug", "title", "sort").
|
||||
From("categories").Limit(uint64(queryOpt.PageSize)).
|
||||
Offset(uint64(queryOpt.Page)).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
for _, opt := range modelOpts {
|
||||
opt(new(sqBuilder))
|
||||
}
|
||||
|
||||
query, args, err := sqBuilder.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("squirrel 构造SQL出错: %w", err)
|
||||
}
|
||||
cacheKey, err := cache.GenCacheKey(m.table, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, hit, err := m.cache.Get[[]*Link](ctx, cacheKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hit {
|
||||
return result, nil
|
||||
}
|
||||
if queryOpt.WithCacheWrite {
|
||||
if err := m.cache.Set(ctx, cache.Entry[[]*Link]{
|
||||
Key: cacheKey,
|
||||
Value: result,
|
||||
TTL: queryOpt.CacheTTL,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func WithCategorySlug(slug string) ModelOption {
|
||||
return func(builder *sq.SelectBuilder) {
|
||||
*builder = builder.Where(sq.Eq{"categories.slug": slug})
|
||||
}
|
||||
}
|
||||
func WithCategoryTitle(title string) ModelOption {
|
||||
return func(builder *sq.SelectBuilder) {
|
||||
*builder = builder.Where(sq.Eq{"categories.title": title})
|
||||
}
|
||||
}
|
||||
|
||||
func WithCategorySort(sort int) ModelOption {
|
||||
return func(builder *sq.SelectBuilder) {
|
||||
*builder = builder.Where(sq.Eq{"categories.sort": sort})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package query
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
defaultPage = 1
|
||||
defaultPageSize = 10
|
||||
defaultIncludeDeleted = false
|
||||
defaultWithCacheWrite = false
|
||||
defaultCacheTTL = 5 * time.Minute
|
||||
defaultNotFoundCacheTTL = 1 * time.Minute
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Page int
|
||||
PageSize int
|
||||
IncludeDeleted bool
|
||||
WithCacheWrite bool
|
||||
CacheTTL time.Duration
|
||||
NotFoundCacheTTL time.Duration
|
||||
}
|
||||
|
||||
type OptionFunc func(*Option)
|
||||
|
||||
func NewOption(opts ...OptionFunc) *Option {
|
||||
queryOpntion := &Option{
|
||||
Page: defaultPage,
|
||||
PageSize: defaultPageSize,
|
||||
IncludeDeleted: defaultIncludeDeleted,
|
||||
WithCacheWrite: defaultWithCacheWrite,
|
||||
CacheTTL: defaultCacheTTL,
|
||||
NotFoundCacheTTL: defaultNotFoundCacheTTL,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(queryOpntion)
|
||||
}
|
||||
return queryOpntion
|
||||
}
|
||||
|
||||
func WithPage(page int) OptionFunc {
|
||||
return func(o *Option) {
|
||||
if page > 0 {
|
||||
o.Page = page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithPageSize(pageSize int) OptionFunc {
|
||||
return func(o *Option) {
|
||||
if pageSize > 0 {
|
||||
o.PageSize = pageSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithIncludeDeleted(includeDeleted bool) OptionFunc {
|
||||
return func(o *Option) {
|
||||
o.IncludeDeleted = includeDeleted
|
||||
}
|
||||
}
|
||||
|
||||
func WithCacheWrite(withCacheWrite bool) OptionFunc {
|
||||
return func(o *Option) {
|
||||
o.WithCacheWrite = withCacheWrite
|
||||
}
|
||||
}
|
||||
|
||||
func WithCacheTTL(cacheTTL time.Duration) OptionFunc {
|
||||
return func(o *Option) {
|
||||
if cacheTTL >= 0 {
|
||||
o.CacheTTL = cacheTTL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithNotFoundCacheTTL(notFoundCacheTTL time.Duration) OptionFunc {
|
||||
return func(o *Option) {
|
||||
if notFoundCacheTTL >= 0 {
|
||||
o.NotFoundCacheTTL = notFoundCacheTTL
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user