98 lines
3.4 KiB
Go
98 lines
3.4 KiB
Go
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
|
||
}
|