368 lines
12 KiB
Go
368 lines
12 KiB
Go
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
|
||
}
|