package repository import ( "context" "fashionapi/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) // 每个用户浏览历史硬上限:超出后 FIFO 删除最旧条目,避免重度用户把表撑爆。 const historyCap = 2000 // HistoryRepository 浏览历史数据访问接口。 type HistoryRepository interface { // Upsert 记录一次浏览:同一 (user_id, target_uid)(一篇文章)已存在时只刷新 viewed_at, // 不新增行;插入/刷新后若超出用户上限,按 viewed_at 升序 FIFO 删最旧。 Upsert(ctx context.Context, h *model.History) error // ListPaged 分页取某用户历史,按 viewed_at 倒序。 ListPaged(ctx context.Context, userID uint32, page, perPage int) ([]model.History, error) // CountByUser 取某用户历史总数(分页用)。 CountByUser(ctx context.Context, userID uint32) (int64, error) // Remove 删除某用户对某篇文章(target_uid)的历史。 Remove(ctx context.Context, userID uint32, targetUID string) error // Clear 清空某用户全部历史。 Clear(ctx context.Context, userID uint32) error } type historyRepository struct { db *gorm.DB } // NewHistoryRepository 创建浏览历史仓储。 func NewHistoryRepository(db *gorm.DB) HistoryRepository { return &historyRepository{db: db} } func (r *historyRepository) Upsert(ctx context.Context, h *model.History) error { // 复合唯一索引 (user_id, target_uid) 保证同一篇文章不重复;冲突时刷新 viewed_at, // 反复打开同一篇文章只更新最后浏览时间,行数不增长。 err := r.db.WithContext(ctx). Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "user_id"}, {Name: "target_uid"}}, DoUpdates: clause.AssignmentColumns([]string{"viewed_at"}), }).Create(h).Error if err != nil { return err } // 超出用户上限则 FIFO 裁剪最旧若干(子查询双嵌套规避数据库「同表不可直接子查删除」限制)。 var n int64 if err := r.db.WithContext(ctx).Model(&model.History{}).Where("user_id = ?", h.UserID).Count(&n).Error; err != nil { return err } if n > historyCap { excess := n - historyCap if err := r.db.WithContext(ctx).Exec( "DELETE FROM histories WHERE user_id = ? AND id IN (SELECT id FROM (SELECT id FROM histories WHERE user_id = ? ORDER BY viewed_at ASC LIMIT ?) AS t)", h.UserID, h.UserID, excess, ).Error; err != nil { return err } } return nil } func (r *historyRepository) ListPaged(ctx context.Context, userID uint32, page, perPage int) ([]model.History, error) { var rows []model.History offset := (page - 1) * perPage if offset < 0 { offset = 0 } err := r.db.WithContext(ctx). Where("user_id = ?", userID). Order("viewed_at DESC"). Offset(offset).Limit(perPage). Find(&rows).Error return rows, err } func (r *historyRepository) CountByUser(ctx context.Context, userID uint32) (int64, error) { var n int64 err := r.db.WithContext(ctx).Model(&model.History{}).Where("user_id = ?", userID).Count(&n).Error return n, err } func (r *historyRepository) Remove(ctx context.Context, userID uint32, targetUID string) error { return r.db.WithContext(ctx). Where("user_id = ? AND target_uid = ?", userID, targetUID). Delete(&model.History{}).Error } func (r *historyRepository) Clear(ctx context.Context, userID uint32) error { return r.db.WithContext(ctx). Where("user_id = ?", userID). Delete(&model.History{}).Error }