From c812cfb663221d195f434d553518a6aa8534be64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=82=B2=E6=96=B0?= <2635131263@qq.com> Date: Wed, 21 Oct 2026 00:11:37 +0800 Subject: [PATCH] =?UTF-8?q?wip(lyra):=20=E4=BF=9D=E5=AD=98=E5=B8=A6?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2=E7=9A=84?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加通用查询选项、分类过滤选项及动态缓存键生成 - 新增分类创建与更新时间字段 当前实现尚未完成,留待后续继续设计: - 明确通用查询选项、模型过滤条件与缓存策略的边界 - 补齐数据库回源,修正分类返回类型及过滤条件生效问题 - 修正分页偏移计算,增加参数边界校验和稳定排序 - 完善缓存故障降级、空结果缓存及写后失效策略 - 修正缓存键参数编码歧义,补齐查询名和版本前缀 - 补充测试并验证编译与查询行为 --- go/projects/lyra/internal/cache/cache.go | 17 ++++ go/projects/lyra/internal/model/category.go | 72 +++++++++++++++-- go/projects/lyra/internal/pkg/query/query.go | 82 ++++++++++++++++++++ 3 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 go/projects/lyra/internal/pkg/query/query.go diff --git a/go/projects/lyra/internal/cache/cache.go b/go/projects/lyra/internal/cache/cache.go index ea7459c..bcb90f8 100644 --- a/go/projects/lyra/internal/cache/cache.go +++ b/go/projects/lyra/internal/cache/cache.go @@ -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 +} diff --git a/go/projects/lyra/internal/model/category.go b/go/projects/lyra/internal/model/category.go index c780dd5..5684636 100644 --- a/go/projects/lyra/internal/model/category.go +++ b/go/projects/lyra/internal/model/category.go @@ -7,6 +7,7 @@ import ( "lyra/internal/cache" "lyra/internal/pkg/constant" + "lyra/internal/pkg/query" "lyra/internal/util" sq "github.com/Masterminds/squirrel" @@ -14,22 +15,26 @@ import ( ) type Category struct { - ID int64 `db:"id"` - Slug string `db:"slug"` - Title string `db:"title"` - Sort int `db:"sort"` + ID int64 `db:"id"` + 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}) + } +} diff --git a/go/projects/lyra/internal/pkg/query/query.go b/go/projects/lyra/internal/pkg/query/query.go new file mode 100644 index 0000000..b52fabb --- /dev/null +++ b/go/projects/lyra/internal/pkg/query/query.go @@ -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 + } + } +}