feat: 修改当前点赞逻辑

This commit is contained in:
zerosaturation 2026-05-20 12:36:58 +08:00
parent 1ee151630d
commit 02598db333
8 changed files with 122 additions and 398 deletions

View File

@ -191,14 +191,17 @@ const (
// ========== 点赞记录表模型 ==========
// AssetLike 点赞记录表模型
// 唯一约束:(asset_id, user_id, exhibition_id) - 每次展览每个用户只能点赞一次
// 注意exhibition_id 不设置外键约束,因为迁移时现有数据没有有效的 exhibition_id
type AssetLike struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
AssetID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_asset;index:idx_asset_likes_asset;column:asset_id"`
UserID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_asset;index:idx_asset_likes_user_star;column:user_id"`
StarID int64 `gorm:"not null;index:idx_asset_likes_user_star;column:star_id"` // 用于数据隔离和查询优化
CreatedAt int64 `gorm:"not null;index:idx_asset_likes_user_star,sort:desc;index:idx_asset_likes_asset,sort:desc;column:created_at"`
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
AssetID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_exhibition;index:idx_asset_likes_asset;column:asset_id"`
UserID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_exhibition;index:idx_asset_likes_user_star;column:user_id"`
StarID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_exhibition;index:idx_asset_likes_user_star;column:star_id"` // 用于数据隔离和查询优化
ExhibitionID int64 `gorm:"not null;uniqueIndex:uk_asset_likes_user_exhibition;index:idx_asset_likes_exhibition;column:exhibition_id"` // 关联展览,同一展览只能点赞一次
CreatedAt int64 `gorm:"not null;index:idx_asset_likes_user_star,sort:desc;index:idx_asset_likes_asset,sort:desc;column:created_at"`
// 关联关系
// 关联关系(不设置外键约束,避免迁移问题)
Asset Asset `gorm:"foreignKey:AssetID;references:ID;constraint:OnDelete:CASCADE"`
User User `gorm:"foreignKey:UserID;references:ID;constraint:OnDelete:CASCADE"`
Star Star `gorm:"foreignKey:StarID;references:StarID;constraint:OnDelete:CASCADE"`

View File

@ -13,10 +13,10 @@ type AssetLikeRepository interface {
Create(like *models.AssetLike) error
// Delete 删除点赞记录
Delete(assetID, userID, starID int64) error
Delete(assetID, userID, starID, exhibitionID int64) error
// Exists 检查点赞记录是否存在
Exists(assetID, userID, starID int64) (bool, error)
// Exists 检查点赞记录是否存在(按 asset_id, user_id, exhibition_id
Exists(assetID, userID, starID, exhibitionID int64) (bool, error)
// GetByAsset 获取资产的点赞记录列表(分页)
GetByAsset(assetID int64, limit, offset int) ([]*models.AssetLike, error)
@ -61,8 +61,8 @@ func (r *assetLikeRepository) Create(like *models.AssetLike) error {
return errors.New("star_id must be greater than 0")
}
// 检查是否已存在
exists, err := r.Exists(like.AssetID, like.UserID, like.StarID)
// 检查是否已存在(同一展览同用户只能点赞一次)
exists, err := r.Exists(like.AssetID, like.UserID, like.StarID, like.ExhibitionID)
if err != nil {
return err
}
@ -78,7 +78,7 @@ func (r *assetLikeRepository) Create(like *models.AssetLike) error {
}
// Delete 删除点赞记录
func (r *assetLikeRepository) Delete(assetID, userID, starID int64) error {
func (r *assetLikeRepository) Delete(assetID, userID, starID, exhibitionID int64) error {
if assetID <= 0 {
return errors.New("asset_id must be greater than 0")
}
@ -91,7 +91,11 @@ func (r *assetLikeRepository) Delete(assetID, userID, starID int64) error {
return errors.New("star_id must be greater than 0")
}
result := r.db.Where("asset_id = ? AND user_id = ? AND star_id = ?", assetID, userID, starID).
if exhibitionID <= 0 {
return errors.New("exhibition_id must be greater than 0")
}
result := r.db.Where("asset_id = ? AND user_id = ? AND star_id = ? AND exhibition_id = ?", assetID, userID, starID, exhibitionID).
Delete(&models.AssetLike{})
if result.Error != nil {
@ -105,8 +109,8 @@ func (r *assetLikeRepository) Delete(assetID, userID, starID int64) error {
return nil
}
// Exists 检查点赞记录是否存在
func (r *assetLikeRepository) Exists(assetID, userID, starID int64) (bool, error) {
// Exists 检查点赞记录是否存在(按 asset_id, user_id, exhibition_id
func (r *assetLikeRepository) Exists(assetID, userID, starID, exhibitionID int64) (bool, error) {
if assetID <= 0 {
return false, errors.New("asset_id must be greater than 0")
}
@ -119,9 +123,13 @@ func (r *assetLikeRepository) Exists(assetID, userID, starID int64) (bool, error
return false, errors.New("star_id must be greater than 0")
}
if exhibitionID <= 0 {
return false, errors.New("exhibition_id must be greater than 0")
}
var count int64
err := r.db.Model(&models.AssetLike{}).
Where("asset_id = ? AND user_id = ? AND star_id = ?", assetID, userID, starID).
Where("asset_id = ? AND user_id = ? AND star_id = ? AND exhibition_id = ?", assetID, userID, starID, exhibitionID).
Count(&count).Error
if err != nil {

View File

@ -1,329 +0,0 @@
package repository
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/topfans/backend/pkg/models"
)
// TestAssetLikeRepository_Create 测试创建点赞记录
func TestAssetLikeRepository_Create(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_create")
user := createTestUser(t, db, "19900200001")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
asset := createTestAsset(t, db, user.ID, star.StarID, "测试资产")
// 测试创建点赞
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
assert.NotZero(t, like.ID)
assert.NotZero(t, like.CreatedAt)
// 测试重复点赞
duplicateLike := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err = repo.Create(duplicateLike)
assert.Error(t, err)
assert.Contains(t, err.Error(), "already liked")
}
// TestAssetLikeRepository_Delete 测试删除点赞记录
func TestAssetLikeRepository_Delete(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_delete")
user := createTestUser(t, db, "19900200002")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
asset := createTestAsset(t, db, user.ID, star.StarID, "测试资产")
// 先创建点赞
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
// 删除点赞
err = repo.Delete(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
// 验证已删除
exists, err := repo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.False(t, exists)
// 测试删除不存在的记录
err = repo.Delete(asset.ID, user.ID, star.StarID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}
// TestAssetLikeRepository_Exists 测试检查点赞是否存在
func TestAssetLikeRepository_Exists(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_exists")
user := createTestUser(t, db, "19900200003")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
asset := createTestAsset(t, db, user.ID, star.StarID, "测试资产")
// 初始状态不存在
exists, err := repo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.False(t, exists)
// 创建点赞后存在
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err = repo.Create(like)
assert.NoError(t, err)
exists, err = repo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.True(t, exists)
}
// TestAssetLikeRepository_GetByAsset 测试获取资产的点赞列表
func TestAssetLikeRepository_GetByAsset(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_by_asset")
// 创建资产的所有者
assetOwner := createTestUser(t, db, "19900200004")
createTestFanProfile(t, db, assetOwner.ID, star.StarID, "资产所有者")
asset := createTestAsset(t, db, assetOwner.ID, star.StarID, "测试资产")
// 创建多个用户点赞
for i := 1; i <= 5; i++ {
mobile := "1990020000" + string(rune('4'+i))
user := createTestUser(t, db, mobile)
createTestFanProfile(t, db, user.ID, star.StarID, fmt.Sprintf("用户%d", i))
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
}
// 获取点赞列表
likes, err := repo.GetByAsset(asset.ID, 3, 0)
assert.NoError(t, err)
assert.Len(t, likes, 3)
// 获取第二页
likes, err = repo.GetByAsset(asset.ID, 3, 3)
assert.NoError(t, err)
assert.Len(t, likes, 2)
}
// TestAssetLikeRepository_GetByUser 测试获取用户的点赞列表
func TestAssetLikeRepository_GetByUser(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_by_user")
user := createTestUser(t, db, "19900200009")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
// 创建多个资产并点赞
for i := 0; i < 5; i++ {
asset := createTestAsset(t, db, user.ID, star.StarID, "资产")
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
}
// 获取点赞列表
likes, err := repo.GetByUser(user.ID, star.StarID, 3, 0)
assert.NoError(t, err)
assert.Len(t, likes, 3)
// 获取第二页
likes, err = repo.GetByUser(user.ID, star.StarID, 3, 3)
assert.NoError(t, err)
assert.Len(t, likes, 2)
}
// TestAssetLikeRepository_CountByAsset 测试统计资产的点赞数
func TestAssetLikeRepository_CountByAsset(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_count_asset")
// 创建资产的所有者
assetOwner := createTestUser(t, db, "19900200010")
createTestFanProfile(t, db, assetOwner.ID, star.StarID, "资产所有者")
asset := createTestAsset(t, db, assetOwner.ID, star.StarID, "测试资产")
// 初始点赞数为0
count, err := repo.CountByAsset(asset.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
// 创建3个点赞
for i := 1; i <= 3; i++ {
mobile := "1990020001" + string(rune('0'+i))
user := createTestUser(t, db, mobile)
createTestFanProfile(t, db, user.ID, star.StarID, fmt.Sprintf("用户%d", i))
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
}
// 验证点赞数
count, err = repo.CountByAsset(asset.ID)
assert.NoError(t, err)
assert.Equal(t, int64(3), count)
}
// TestAssetLikeRepository_CountByUser 测试统计用户的点赞数
func TestAssetLikeRepository_CountByUser(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewAssetLikeRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_count_user")
user := createTestUser(t, db, "19900200014")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
// 初始点赞数为0
count, err := repo.CountByUser(user.ID, star.StarID)
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
// 创建3个点赞
for i := 0; i < 3; i++ {
asset := createTestAsset(t, db, user.ID, star.StarID, "资产")
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err := repo.Create(like)
assert.NoError(t, err)
}
// 验证点赞数
count, err = repo.CountByUser(user.ID, star.StarID)
assert.NoError(t, err)
assert.Equal(t, int64(3), count)
}
// TestAssetLikeRepository_LikeUnlikeFlow 测试点赞-取消点赞流程
func TestAssetLikeRepository_LikeUnlikeFlow(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
assetLikeRepo := NewAssetLikeRepository(db)
assetRepo := NewAssetRepository(db)
// 创建测试数据
star := createTestStar(t, db, "test_like_flow")
user := createTestUser(t, db, "19900200015")
createTestFanProfile(t, db, user.ID, star.StarID, "测试用户")
asset := createTestAsset(t, db, user.ID, star.StarID, "测试资产")
// 1. 初始状态
exists, err := assetLikeRepo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.False(t, exists)
assetData, err := assetRepo.GetByID(asset.ID)
assert.NoError(t, err)
assert.Equal(t, int32(0), assetData.LikeCount)
// 2. 点赞
like := &models.AssetLike{
AssetID: asset.ID,
UserID: user.ID,
StarID: star.StarID,
}
err = assetLikeRepo.Create(like)
assert.NoError(t, err)
// 增加资产点赞数
err = assetRepo.IncrementLikeCount(asset.ID)
assert.NoError(t, err)
// 验证点赞状态
exists, err = assetLikeRepo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.True(t, exists)
assetData, err = assetRepo.GetByID(asset.ID)
assert.NoError(t, err)
assert.Equal(t, int32(1), assetData.LikeCount)
// 3. 取消点赞
err = assetLikeRepo.Delete(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
// 减少资产点赞数
err = assetRepo.DecrementLikeCount(asset.ID)
assert.NoError(t, err)
// 验证取消点赞状态
exists, err = assetLikeRepo.Exists(asset.ID, user.ID, star.StarID)
assert.NoError(t, err)
assert.False(t, exists)
assetData, err = assetRepo.GetByID(asset.ID)
assert.NoError(t, err)
assert.Equal(t, int32(0), assetData.LikeCount)
}

View File

@ -54,6 +54,9 @@ type AssetRepository interface {
// IsExhibiting 检查资产是否正在展出中
IsExhibiting(assetID int64) (bool, error)
// GetExhibitingID 获取正在展出的展览ID如果正在展出返回0表示未展出
GetExhibitingID(assetID int64) (int64, error)
// GetExhibitionStartTime 获取资产展出开始时间(如果正在展出)
GetExhibitionStartTime(assetID int64) (int64, error)
@ -367,6 +370,30 @@ func (r *assetRepository) IsExhibiting(assetID int64) (bool, error) {
return count > 0, nil
}
// GetExhibitingID 获取正在展出的展览ID如果正在展出返回0表示未展出
func (r *assetRepository) GetExhibitingID(assetID int64) (int64, error) {
if assetID <= 0 {
return 0, errors.New("asset_id must be greater than 0")
}
var exhibition struct {
ID int64
}
err := r.db.Model(&models.Exhibition{}).
Select("id").
Where("asset_id = ? AND expire_at > ?", assetID, time.Now().UnixMilli()).
First(&exhibition).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, nil // 未展出
}
return 0, err
}
return exhibition.ID, nil
}
// GetExhibitionStartTime 获取资产展出开始时间(如果正在展出)
func (r *assetRepository) GetExhibitionStartTime(assetID int64) (int64, error) {
if assetID <= 0 {

View File

@ -33,17 +33,20 @@ func NewAssetLikeService(
}
}
// isAssetExhibiting 检查资产当前是否在展出中
func (s *AssetLikeService) isAssetExhibiting(assetID int64) (bool, error) {
// isAssetExhibiting 检查资产当前是否在展出中,返回 exhibition_id
func (s *AssetLikeService) isAssetExhibiting(assetID int64) (int64, error) {
nowMs := time.Now().UnixMilli()
var count int64
err := s.db.Table("exhibitions").
Where("asset_id = ? AND expire_at > ?", assetID, nowMs).
Count(&count).Error
if err != nil {
return false, fmt.Errorf("failed to check exhibition status: %w", err)
var exhibition struct {
ID int64
}
return count > 0, nil
err := s.db.Table("exhibitions").
Select("id").
Where("asset_id = ? AND expire_at > ?", assetID, nowMs).
First(&exhibition).Error
if err != nil {
return 0, fmt.Errorf("failed to check exhibition status: %w", err)
}
return exhibition.ID, nil
}
// isUniqueConstraintViolation 检查错误是否为唯一约束冲突PostgreSQL error code 23505
@ -116,8 +119,8 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
return 0, fmt.Errorf("asset not found: %w", err)
}
// 1.5 检查资产是否当前在展出中
exhibiting, err := s.isAssetExhibiting(assetID)
// 1.5 检查资产是否当前在展出中,获取 exhibition_id
exhibitionID, err := s.isAssetExhibiting(assetID)
if err != nil {
logger.Logger.Error("Failed to check exhibition status",
zap.Error(err),
@ -125,12 +128,12 @@ func (s *AssetLikeService) LikeAsset(ctx context.Context, assetID, userID, starI
)
return 0, err
}
if !exhibiting {
if exhibitionID == 0 {
return 0, fmt.Errorf("资产未在展示中,无法点赞")
}
// 2. 检查是否已点赞
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID)
// 2. 检查是否已点赞(同一展览同用户只能点赞一次)
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID, exhibitionID)
if err != nil {
logger.Logger.Error("Failed to check if already liked",
zap.Error(err),
@ -229,8 +232,8 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
zap.Int64("star_id", starID),
)
// 0. 检查资产是否当前在展出中(展出结束后不允许取消点赞)
exhibiting, err := s.isAssetExhibiting(assetID)
// 0. 检查资产是否当前在展出中,获取 exhibition_id(展出结束后不允许取消点赞)
exhibitionID, err := s.isAssetExhibiting(assetID)
if err != nil {
logger.Logger.Error("Failed to check exhibition status",
zap.Error(err),
@ -238,12 +241,12 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
)
return 0, err
}
if !exhibiting {
if exhibitionID == 0 {
return 0, fmt.Errorf("资产未在展示中,无法取消点赞")
}
// 1. 检查是否已点赞
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID)
// 1. 检查是否已点赞(同一展览同用户只能点赞一次)
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID, exhibitionID)
if err != nil {
logger.Logger.Error("Failed to check if liked",
zap.Error(err),
@ -262,8 +265,8 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
// 2. 开启事务
err = s.db.Transaction(func(tx *gorm.DB) error {
// 2.1 删除点赞记录
if err := tx.Where("asset_id = ? AND user_id = ? AND star_id = ?", assetID, userID, starID).
// 2.1 删除点赞记录(按 exhibition_id 删除)
if err := tx.Where("asset_id = ? AND user_id = ? AND star_id = ? AND exhibition_id = ?", assetID, userID, starID, exhibitionID).
Delete(&models.AssetLike{}).Error; err != nil {
logger.Logger.Error("Failed to delete like record",
zap.Error(err),
@ -336,7 +339,7 @@ func (s *AssetLikeService) UnlikeAsset(ctx context.Context, assetID, userID, sta
return asset.LikeCount, nil
}
// CheckAssetLike 检查是否已点赞
// CheckAssetLike 检查是否已点赞(在当前展出中)
func (s *AssetLikeService) CheckAssetLike(ctx context.Context, assetID, userID, starID int64) (bool, error) {
logger.Logger.Debug("AssetLikeService.CheckAssetLike called",
zap.Int64("asset_id", assetID),
@ -344,7 +347,22 @@ func (s *AssetLikeService) CheckAssetLike(ctx context.Context, assetID, userID,
zap.Int64("star_id", starID),
)
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID)
// 获取当前展出中的 exhibition_id
exhibitionID, err := s.isAssetExhibiting(assetID)
if err != nil {
logger.Logger.Error("Failed to check exhibition status",
zap.Error(err),
zap.Int64("asset_id", assetID),
)
return false, fmt.Errorf("failed to check exhibition status: %w", err)
}
if exhibitionID == 0 {
// 资产不在展出中,视为未点赞
return false, nil
}
exists, err := s.assetLikeRepo.Exists(assetID, userID, starID, exhibitionID)
if err != nil {
logger.Logger.Error("Failed to check like status",
zap.Error(err),

View File

@ -452,17 +452,21 @@ func (s *assetService) GetAsset(req *pb.GetAssetRequest, userID, starID int64) (
ownerNickname = profile.Nickname
}
// 4. 检查当前用户是否已点赞
isLiked, err := s.assetLikeRepo.Exists(asset.ID, userID, starID)
if err != nil {
logger.Logger.Warn("Failed to check like status, will return is_liked as false",
zap.Int64("asset_id", asset.ID),
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Error(err),
)
// 检查失败时,默认为未点赞
isLiked = false
// 4. 检查当前用户是否已点赞(需要获取当前展出中的 exhibition_id
exhibitionID, _ := s.assetRepo.GetExhibitingID(asset.ID)
isLiked := false
if exhibitionID > 0 {
isLiked, err = s.assetLikeRepo.Exists(asset.ID, userID, starID, exhibitionID)
if err != nil {
logger.Logger.Warn("Failed to check like status, will return is_liked as false",
zap.Int64("asset_id", asset.ID),
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Error(err),
)
// 检查失败时,默认为未点赞
isLiked = false
}
}
// 5. 从 asset_registry 表获取 display_status

View File

@ -156,17 +156,8 @@ func (w *CleanupWorker) cleanupExpiredExhibitions(now int64) {
log.Printf("展品已到期并生成领取记录: ExhibitionID=%d, AssetID=%d, SlotID=%d, OccupierUID=%d, Revenue=%d",
e.ID, e.AssetID, e.SlotID, e.OccupierUID, revenue)
// 5. 清除该资产的点赞记录(不阻断主流程),允许用户在下次展出时再次点赞
assetID := e.AssetID
go func() {
if w.assetClient != nil {
if err := w.assetClient.ClearAssetLikeRecords(assetID); err != nil {
logger.Logger.Error("清除过期展品点赞记录失败",
zap.Int64("asset_id", assetID),
zap.Error(err))
}
}
}()
// 5. 保留点赞记录,允许用户在下次展出时再次点赞(每次展览可点赞一次)
// 注意:点赞记录现在按 (asset_id, user_id, exhibition_id) 去重,不会与历史点赞冲突
}
log.Printf("过期展品清理完成: 成功 %d 个, 失败 %d 个", successCount, failedCount)

View File

@ -593,9 +593,10 @@ func (r *socialRepositoryImpl) GetMyLikedAssets(userID, starID int64, page, page
// 计数查询(使用 DISTINCT 因为一个资产可能在多个展位展出)
// 显示展品未下架deleted_at IS NULL无论是否过期
// 不显示已领取下架的deleted_at IS NOT NULL或从未展出过的
// 使用 asset_likes.exhibition_id 关联,确保只返回有效展览的点赞记录
countQuery := r.db.Model(&models.AssetLike{}).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id AND e.deleted_at IS NULL").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id AND e.deleted_at IS NULL").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("a.deleted_at IS NULL AND a.is_active = ?", true)
@ -604,13 +605,14 @@ func (r *socialRepositoryImpl) GetMyLikedAssets(userID, starID int64, page, page
}
// 数据查询:只过滤已下架的,不过滤过期(用户领取收益后才下架)
// 使用 asset_likes.exhibition_id 关联,确保只返回当前有效展览的点赞记录
offset := (page - 1) * pageSize
err := r.db.Model(&models.AssetLike{}).
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
asset_likes.created_at as liked_at,
(a.tags @> '["craft:lenticular"]') as is_lenticular`).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id AND e.deleted_at IS NULL").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id AND e.deleted_at IS NULL").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
Group("asset_likes.asset_id, a.name, a.cover_url, a.like_count, asset_likes.created_at, is_lenticular").
@ -692,7 +694,7 @@ func (r *socialRepositoryImpl) GetMyTodayLikedAssets(userID, starID int64, page,
countQuery := r.db.Model(&models.AssetLike{}).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("asset_likes.created_at >= ?", startOfDay).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
@ -707,7 +709,7 @@ func (r *socialRepositoryImpl) GetMyTodayLikedAssets(userID, starID int64, page,
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
asset_likes.created_at as liked_at`).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("asset_likes.created_at >= ?", startOfDay).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
@ -752,7 +754,7 @@ func (r *socialRepositoryImpl) GetMyWeekLikedAssets(userID, starID int64, page,
countQuery := r.db.Model(&models.AssetLike{}).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("asset_likes.created_at >= ?", startOfWeekMillis).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
@ -767,7 +769,7 @@ func (r *socialRepositoryImpl) GetMyWeekLikedAssets(userID, starID int64, page,
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
asset_likes.created_at as liked_at`).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("asset_likes.created_at >= ?", startOfWeekMillis).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
@ -805,7 +807,7 @@ func (r *socialRepositoryImpl) GetUserLikedAssets(userID, starID int64, page, pa
countQuery := r.db.Model(&models.AssetLike{}).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
Where("e.deleted_at IS NULL AND e.expire_at > ?", now)
@ -819,7 +821,7 @@ func (r *socialRepositoryImpl) GetUserLikedAssets(userID, starID int64, page, pa
Select(`asset_likes.asset_id, a.name, a.cover_url, a.like_count,
asset_likes.created_at as liked_at`).
Joins("JOIN assets a ON a.id = asset_likes.asset_id").
Joins("JOIN exhibitions e ON e.asset_id = a.id").
Joins("JOIN exhibitions e ON e.id = asset_likes.exhibition_id").
Where("asset_likes.user_id = ? AND asset_likes.star_id = ?", userID, starID).
Where("a.deleted_at IS NULL AND a.is_active = ?", true).
Where("e.deleted_at IS NULL AND e.expire_at > ?", now).