package model import ( "context" "fmt" "time" "lyra/internal/cache" "lyra/internal/pkg/constant" "lyra/internal/pkg/query" "lyra/internal/util" sq "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" ) type Category struct { 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, table: "categories", cache: cache} } const categoryListCacheTTL = 5 * time.Minute func (m *defaultCategoryModel) FindAll(ctx context.Context) ([]*Category, error) { cached, hit, err := m.cache.Get[[]*Category](ctx, constant.CategoryListCacheKey) if err != nil { util.Logger(ctx).Error("读取分类列表缓存失败", "key", constant.CategoryListCacheKey, "error", err) } if hit { return cached, nil } query, args, err := sq. Select("id", "slug", "title", "sort"). From("public.categories"). Where(sq.GtOrEq{"sort": 1}). OrderBy("sort ASC", "id ASC"). Limit(10). PlaceholderFormat(sq.Dollar). ToSql() if err != nil { return nil, fmt.Errorf("squirrel 出错: %w", err) } rows := make([]*Category, 0) err = m.db.SelectContext(ctx, &rows, query, args...) if err != nil { return nil, fmt.Errorf("数据库查询出错: %w", err) } if err := m.cache.Set(ctx, cache.Entry[[]*Category]{ Key: constant.CategoryListCacheKey, Value: rows, TTL: categoryListCacheTTL, }); err != nil { util.Logger(ctx).Error("写入分类列表缓存失败", "key", constant.CategoryListCacheKey, "error", err) } 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}) } }