This commit is contained in:
toom1996
2026-09-02 21:51:35 +08:00
parent 2562fc4aaa
commit 1fd9c48f58
22 changed files with 908 additions and 30 deletions

View File

@ -0,0 +1,97 @@
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 裁剪最旧若干(子查询双嵌套规避 MySQL「同表不可直接子查删除」限制)。
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
}