topfans/backend/services/galleryService/repository/gallery_repository.go
2026-04-30 21:21:12 +08:00

368 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"errors"
"time"
"github.com/topfans/backend/pkg/models"
"github.com/topfans/backend/services/galleryService/config"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// GalleryRepository 展馆数据访问层接口
type GalleryRepository interface {
// 展位相关
GetSlotsByUser(userID, starID int64) ([]*models.BoothSlot, error)
GetSlotByID(slotID int64) (*models.BoothSlot, error)
GetSlotCount(userID, starID int64) (int64, error)
CreateInitialSlots(userID, starID, hostProfileID int64) error
CreateSlot(slot *models.BoothSlot) error
UnlockSlot(slotID int64) error
// 展品相关
GetExhibitionByAsset(assetID int64) (*models.Exhibition, error)
GetExhibitionBySlot(slotID int64) (*models.Exhibition, error)
GetExhibitionsByUser(userID, starID int64) ([]*models.Exhibition, error)
CreateExhibition(exhibition *models.Exhibition) error
DeleteExhibition(exhibitionID int64) error
DeleteExhibitionByAsset(assetID int64) error
GetExpiredExhibitions(beforeTime int64) ([]*models.Exhibition, error)
// 资产注册表相关
UpdateAssetRegistryDisplayStatus(assetID int64, displayStatus int32) error
// ========== 我的作品相关 ==========
// GetMyExhibitedAssets 获取我展出的作品列表(只返回展出中且未过期的,含收益)
// userID: 用户ID
// starID: 明星ID
// page: 页码从1开始
// pageSize: 每页数量
// 返回: 作品列表、总数量
GetMyExhibitedAssets(userID, starID int64, page, pageSize int) ([]*ExhibitedAssetInfo, int64, error)
// ========== 灵感瀑布相关 ==========
// CountValidExhibitions 统计有效展品数量
// starID: 明星ID
// materialType: 素材类型过滤(空字符串表示不过滤)
CountValidExhibitions(starID int64, materialType string) (int64, error)
// GetRandomExhibitions 获取随机展品列表
// starID: 明星ID
// materialType: 素材类型过滤(空字符串表示不过滤)
// excludeIDs: 排除的展品ID列表用于去重
// limit: 返回数量
// offset: 偏移量(随机生成)
GetRandomExhibitions(starID int64, materialType string, excludeIDs []int64, limit, offset int) ([]*InspirationFlowItem, error)
}
// InspirationFlowItem 灵感瀑布展品项
type InspirationFlowItem struct {
AssetID int64
Name string
CoverURL string
LikeCount int32
OwnerNickname string
}
// ExhibitedAssetInfo 我展出的作品信息
type ExhibitedAssetInfo struct {
AssetID int64
Name string
CoverURL string
LikeCount int32
ExhibitedAt int64
ExpireAt int64
Earnings int64
}
// galleryRepository Repository实现
type galleryRepository struct {
db *gorm.DB
}
// NewGalleryRepository 创建Repository实例
func NewGalleryRepository(db *gorm.DB) GalleryRepository {
return &galleryRepository{db: db}
}
// ==================== 展位相关 ====================
// GetSlotsByUser 获取用户的所有展位
func (r *galleryRepository) GetSlotsByUser(userID, starID int64) ([]*models.BoothSlot, error) {
var slots []*models.BoothSlot
err := r.db.Where("user_id = ? AND star_id = ?", userID, starID).
Order("slot_index ASC").
Find(&slots).Error
return slots, err
}
// GetSlotByID 根据ID获取展位
func (r *galleryRepository) GetSlotByID(slotID int64) (*models.BoothSlot, error) {
var slot models.BoothSlot
err := r.db.Where("slot_id = ?", slotID).First(&slot).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("展位不存在")
}
return nil, err
}
return &slot, nil
}
// GetSlotCount 获取用户的展位数量
func (r *galleryRepository) GetSlotCount(userID, starID int64) (int64, error) {
var count int64
err := r.db.Model(&models.BoothSlot{}).
Where("user_id = ? AND star_id = ?", userID, starID).
Count(&count).Error
return count, err
}
// CreateInitialSlots 创建初始展位(懒加载,支持并发安全)
func (r *galleryRepository) CreateInitialSlots(userID, starID int64, hostProfileID int64) error {
// 使用 PostgreSQL 的 ON CONFLICT 保证并发安全性
return r.db.Transaction(func(tx *gorm.DB) error {
now := time.Now().UnixMilli()
initialSlotCount := config.GalleryRules.InitialSlotCount
for i := 1; i <= initialSlotCount; i++ {
vis := "public"
if i > 3 {
vis = "private"
}
slot := &models.BoothSlot{
HostProfileID: hostProfileID,
UserID: userID,
StarID: starID,
SlotIndex: i,
Visibility: vis,
IsEnabled: true,
UnlockType: "free",
UnlockValue: 0,
CreatedAt: now,
UpdatedAt: now,
}
// 使用 Clause 处理冲突,确保幂等性
err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "host_profile_id"}, {Name: "slot_index"}},
DoNothing: true,
}).Create(slot).Error
if err != nil {
return err
}
}
return nil
})
}
// CreateSlot 创建新展位(用于解锁)
func (r *galleryRepository) CreateSlot(slot *models.BoothSlot) error {
now := time.Now().UnixMilli()
slot.CreatedAt = now
slot.UpdatedAt = now
return r.db.Create(slot).Error
}
// UnlockSlot 解锁展位
func (r *galleryRepository) UnlockSlot(slotID int64) error {
now := time.Now().UnixMilli()
return r.db.Model(&models.BoothSlot{}).
Where("slot_id = ?", slotID).
Updates(map[string]interface{}{
"is_enabled": true,
"updated_at": now,
}).Error
}
// ==================== 展品相关 ====================
// GetExhibitionByAsset 根据资产ID获取展品展示记录不含已删除
func (r *galleryRepository) GetExhibitionByAsset(assetID int64) (*models.Exhibition, error) {
var exhibition models.Exhibition
err := r.db.Where("asset_id = ? AND deleted_at IS NULL", assetID).First(&exhibition).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 未找到记录,返回 nil不是错误
}
return nil, err
}
return &exhibition, nil
}
// GetExhibitionBySlot 根据展位ID获取展品展示记录不含已删除
func (r *galleryRepository) GetExhibitionBySlot(slotID int64) (*models.Exhibition, error) {
var exhibition models.Exhibition
err := r.db.Where("slot_id = ? AND deleted_at IS NULL", slotID).First(&exhibition).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 未找到记录,返回 nil不是错误
}
return nil, err
}
return &exhibition, nil
}
// GetExhibitionsByUser 获取用户的所有展品展示记录(不含已删除)
func (r *galleryRepository) GetExhibitionsByUser(userID, starID int64) ([]*models.Exhibition, error) {
var exhibitions []*models.Exhibition
err := r.db.Where("occupier_uid = ? AND occupier_star_id = ? AND deleted_at IS NULL", userID, starID).
Find(&exhibitions).Error
return exhibitions, err
}
// CreateExhibition 创建展品展示记录
func (r *galleryRepository) CreateExhibition(exhibition *models.Exhibition) error {
now := time.Now().UnixMilli()
exhibition.CreatedAt = now
exhibition.UpdatedAt = now
return r.db.Create(exhibition).Error
}
// DeleteExhibition 软删除展品展示记录根据ID
func (r *galleryRepository) DeleteExhibition(exhibitionID int64) error {
now := time.Now().UnixMilli()
return r.db.Model(&models.Exhibition{}).
Where("id = ?", exhibitionID).
Updates(map[string]interface{}{
"deleted_at": now,
"updated_at": now,
}).Error
}
// DeleteExhibitionByAsset 软删除展品展示记录根据资产ID
func (r *galleryRepository) DeleteExhibitionByAsset(assetID int64) error {
now := time.Now().UnixMilli()
return r.db.Model(&models.Exhibition{}).
Where("asset_id = ?", assetID).
Updates(map[string]interface{}{
"deleted_at": now,
"updated_at": now,
}).Error
}
// GetExpiredExhibitions 获取过期的展品展示记录(不含已删除)
func (r *galleryRepository) GetExpiredExhibitions(beforeTime int64) ([]*models.Exhibition, error) {
var exhibitions []*models.Exhibition
err := r.db.Where("expire_at <= ? AND deleted_at IS NULL", beforeTime).Find(&exhibitions).Error
return exhibitions, err
}
// UpdateAssetRegistryDisplayStatus 更新资产注册表的展示状态
func (r *galleryRepository) UpdateAssetRegistryDisplayStatus(assetID int64, displayStatus int32) error {
return r.db.Model(&models.AssetRegistry{}).
Where("asset_id = ?", assetID).
Update("display_status", displayStatus).Error
}
// ========== 我的作品相关实现 ==========
// GetMyExhibitedAssets 获取我展出的作品列表(只返回展出中且未过期的,含收益)
func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pageSize int) ([]*ExhibitedAssetInfo, int64, error) {
var items []*ExhibitedAssetInfo
var total int64
now := time.Now().UnixMilli()
// 计数查询
err := r.db.Model(&models.Exhibition{}).
Where("occupier_uid = ? AND occupier_star_id = ? AND deleted_at IS NULL AND expire_at > ?", userID, starID, now).
Count(&total).Error
if err != nil {
return nil, 0, err
}
// 数据查询
offset := (page - 1) * pageSize
err = r.db.Model(&models.Exhibition{}).
Raw(`
SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count,
exhibitions.start_time as exhibited_at, exhibitions.expire_at,
COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings
FROM exhibitions
JOIN assets a ON a.id = exhibitions.asset_id
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?
AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?
GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at
ORDER BY exhibitions.start_time DESC
LIMIT ? OFFSET ?
`, userID, starID, now, pageSize, offset).Scan(&items).Error
if err != nil {
return nil, 0, err
}
return items, total, nil
}
// ========== 灵感瀑布相关实现 ==========
// CountValidExhibitions 统计有效展品数量
func (r *galleryRepository) CountValidExhibitions(starID int64, materialType string) (int64, error) {
var count int64
now := time.Now().UnixMilli()
query := r.db.Model(&models.Exhibition{}).
Where("occupier_star_id = ? AND expire_at > ? AND deleted_at IS NULL", starID, now)
if materialType != "" && materialType != "all" {
query = query.Joins("JOIN assets a ON a.id = exhibitions.asset_id").
Where("a.material_type = ?", materialType)
}
err := query.Count(&count).Error
return count, err
}
// GetRandomExhibitions 获取随机展品列表
func (r *galleryRepository) GetRandomExhibitions(starID int64, materialType string, excludeIDs []int64, limit, offset int) ([]*InspirationFlowItem, error) {
var items []*InspirationFlowItem
now := time.Now().UnixMilli()
// 构建基础查询
baseQuery := r.db.Model(&models.Exhibition{}).
Where("exhibitions.occupier_star_id = ? AND exhibitions.expire_at > ? AND exhibitions.deleted_at IS NULL", starID, now)
if materialType != "" && materialType != "all" {
baseQuery = baseQuery.Joins("JOIN assets a ON a.id = exhibitions.asset_id").
Where("a.material_type = ?", materialType)
}
// 排除已展示的ID
if len(excludeIDs) > 0 {
baseQuery = baseQuery.Where("exhibitions.id NOT IN ?", excludeIDs)
}
// 执行随机排序查询
err := baseQuery.
Select(`exhibitions.asset_id, a.name, a.cover_url, a.like_count, fp.nickname as owner_nickname`).
Joins("JOIN assets a ON a.id = exhibitions.asset_id").
Joins("JOIN fan_profiles fp ON exhibitions.occupier_uid = fp.user_id AND exhibitions.occupier_star_id = fp.star_id").
Where("a.status = 1 AND a.is_active = true").
Order("RANDOM()").
Limit(limit).
Offset(offset).
Scan(&items).Error
if err != nil {
return nil, err
}
return items, nil
}
// ==================== 辅助函数 ====================
// generateHostProfileID 生成 host_profile_id
// 注意:这里使用简单的生成逻辑,实际应该与 fan_profiles 表的逻辑一致
func generateHostProfileID(userID, starID int64) int64 {
// 使用简单的组合方式userID * 1000000 + starID
// 实际项目中应该使用与 User Service 一致的逻辑
return userID*1000000 + starID
}