439 lines
12 KiB
Go
439 lines
12 KiB
Go
package service
|
||
|
||
import (
|
||
"sort"
|
||
|
||
appErrors "github.com/topfans/backend/pkg/errors"
|
||
"github.com/topfans/backend/pkg/models"
|
||
pb "github.com/topfans/backend/pkg/proto/starbook"
|
||
assetRepo "github.com/topfans/backend/services/assetService/repository"
|
||
starbookRepo "github.com/topfans/backend/services/starbookService/repository"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// StarbookService 星册服务接口
|
||
type StarbookService interface {
|
||
// GetStarbookHome 获取星册首页数据
|
||
GetStarbookHome(ownerUID, starID int64) (*pb.GetStarbookHomeResponse, error)
|
||
|
||
// GetStarbookItems 获取星册藏品列表(分页)
|
||
GetStarbookItems(req *pb.GetStarbookItemsRequest, ownerUID, starID int64) (*pb.GetStarbookItemsResponse, error)
|
||
}
|
||
|
||
// starbookService 星册服务实现
|
||
type starbookService struct {
|
||
db *gorm.DB
|
||
registryRepo starbookRepo.AssetRegistryRepository
|
||
assetRepo assetRepo.AssetRepository
|
||
collectionRepo starbookRepo.CollectionRepository
|
||
activityRepo starbookRepo.ActivityAssetRepository
|
||
}
|
||
|
||
// NewStarbookService 创建星册服务实例
|
||
func NewStarbookService(
|
||
db *gorm.DB,
|
||
registryRepo starbookRepo.AssetRegistryRepository,
|
||
assetRepo assetRepo.AssetRepository,
|
||
collectionRepo starbookRepo.CollectionRepository,
|
||
activityRepo starbookRepo.ActivityAssetRepository,
|
||
) StarbookService {
|
||
return &starbookService{
|
||
db: db,
|
||
registryRepo: registryRepo,
|
||
assetRepo: assetRepo,
|
||
collectionRepo: collectionRepo,
|
||
activityRepo: activityRepo,
|
||
}
|
||
}
|
||
|
||
// 常量
|
||
const (
|
||
HomePageSize = 3 // 首页每组最多显示数量
|
||
CastloveCategory = "castlove"
|
||
CategoryNameRegular = "原创"
|
||
CategoryNameCollection = "典藏"
|
||
CategoryNameActivity = "活动"
|
||
)
|
||
|
||
// GetStarbookHome 获取星册首页数据
|
||
func (s *starbookService) GetStarbookHome(ownerUID, starID int64) (*pb.GetStarbookHomeResponse, error) {
|
||
// 1. 查询所有索引记录
|
||
registries, err := s.registryRepo.GetByOwner(ownerUID, starID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 2. 按 type 分组
|
||
typeGroups := make(map[string][]*models.AssetRegistry)
|
||
for _, reg := range registries {
|
||
typeGroups[reg.AssetType] = append(typeGroups[reg.AssetType], reg)
|
||
}
|
||
|
||
// 3. 构建响应
|
||
groups := make([]*pb.AssetGroup, 0)
|
||
|
||
// 处理原创藏品 (regular)
|
||
if regs, ok := typeGroups[models.AssetTypeRegular]; ok {
|
||
group := s.buildRegularGroup(ownerUID, starID, regs)
|
||
if group != nil {
|
||
groups = append(groups, group)
|
||
}
|
||
}
|
||
|
||
// 处理典藏藏品 (collection)
|
||
if regs, ok := typeGroups[models.AssetTypeCollection]; ok {
|
||
group := s.buildCollectionGroup(ownerUID, starID, regs)
|
||
if group != nil {
|
||
groups = append(groups, group)
|
||
}
|
||
}
|
||
|
||
// 处理活动藏品 (activity)
|
||
if regs, ok := typeGroups[models.AssetTypeActivity]; ok {
|
||
group := s.buildActivityGroup(ownerUID, starID, regs)
|
||
if group != nil {
|
||
groups = append(groups, group)
|
||
}
|
||
}
|
||
|
||
return &pb.GetStarbookHomeResponse{
|
||
Data: &pb.StarbookHomeData{
|
||
Groups: groups,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// buildRegularGroup 构建原创藏品分组
|
||
func (s *starbookService) buildRegularGroup(ownerUID, starID int64, registries []*models.AssetRegistry) *pb.AssetGroup {
|
||
// 按 grade 分组
|
||
gradeGroups := make(map[int32][]*models.AssetRegistry)
|
||
for _, reg := range registries {
|
||
if reg.Grade != nil {
|
||
gradeGroups[*reg.Grade] = append(gradeGroups[*reg.Grade], reg)
|
||
}
|
||
}
|
||
|
||
// 构建 GradeSection
|
||
grades := make([]*pb.GradeSection, 0)
|
||
for grade, regs := range gradeGroups {
|
||
// 排序:按点赞数降序,保留前3
|
||
sort.Slice(regs, func(i, j int) bool {
|
||
return regs[i].LikeCount > regs[j].LikeCount
|
||
})
|
||
|
||
// 截取前3条(点赞数最高的3个)
|
||
displayRegs := regs
|
||
hasMore := false
|
||
if len(regs) > HomePageSize {
|
||
displayRegs = regs[:HomePageSize]
|
||
hasMore = true
|
||
}
|
||
|
||
// 获取资产详情并生成预签名URL
|
||
items := s.buildAssetItemsFromRegistries(displayRegs, models.AssetTypeRegular)
|
||
|
||
gradeSection := &pb.GradeSection{
|
||
Grade: grade,
|
||
Items: items,
|
||
TotalCount: int32(len(regs)),
|
||
HasMore: hasMore,
|
||
}
|
||
grades = append(grades, gradeSection)
|
||
}
|
||
|
||
// 按 grade 降序排序
|
||
sort.Slice(grades, func(i, j int) bool {
|
||
return grades[i].Grade > grades[j].Grade
|
||
})
|
||
|
||
// 计算 total_count 和 has_more
|
||
totalCount := int32(0)
|
||
hasMore := false
|
||
for _, g := range grades {
|
||
totalCount += g.TotalCount
|
||
if g.HasMore {
|
||
hasMore = true
|
||
}
|
||
}
|
||
|
||
return &pb.AssetGroup{
|
||
Type: models.AssetTypeRegular,
|
||
Category: CastloveCategory,
|
||
CategoryName: CategoryNameRegular,
|
||
Grades: grades,
|
||
TotalCount: totalCount,
|
||
HasMore: hasMore,
|
||
}
|
||
}
|
||
|
||
// buildCollectionGroup 构建典藏藏品分组
|
||
func (s *starbookService) buildCollectionGroup(ownerUID, starID int64, registries []*models.AssetRegistry) *pb.AssetGroup {
|
||
// 按 category 分组
|
||
categoryGroups := make(map[string][]*models.AssetRegistry)
|
||
for _, reg := range registries {
|
||
if reg.CollectionCategory != nil && *reg.CollectionCategory != "" {
|
||
categoryGroups[*reg.CollectionCategory] = append(categoryGroups[*reg.CollectionCategory], reg)
|
||
}
|
||
}
|
||
|
||
// 构建 AssetGroup items
|
||
allItems := make([]*pb.AssetItem, 0)
|
||
for category, regs := range categoryGroups {
|
||
// 排序:按创建时间降序
|
||
sort.Slice(regs, func(i, j int) bool {
|
||
return regs[i].CreatedAt > regs[j].CreatedAt
|
||
})
|
||
|
||
// 截取前3条(点赞数最高的3个)
|
||
displayRegs := regs
|
||
if len(regs) > HomePageSize {
|
||
displayRegs = regs[:HomePageSize]
|
||
}
|
||
|
||
// 获取资产详情
|
||
items := s.buildAssetItemsFromRegistries(displayRegs, models.AssetTypeCollection)
|
||
for _, item := range items {
|
||
item.Category = category
|
||
}
|
||
allItems = append(allItems, items...)
|
||
}
|
||
|
||
// 计算 total_count 和 has_more
|
||
totalCount := int32(len(registries))
|
||
hasMore := len(registries) > HomePageSize
|
||
|
||
return &pb.AssetGroup{
|
||
Type: models.AssetTypeCollection,
|
||
Category: "",
|
||
CategoryName: CategoryNameCollection,
|
||
Items: allItems,
|
||
TotalCount: totalCount,
|
||
HasMore: hasMore,
|
||
}
|
||
}
|
||
|
||
// buildActivityGroup 构建活动藏品分组
|
||
func (s *starbookService) buildActivityGroup(ownerUID, starID int64, registries []*models.AssetRegistry) *pb.AssetGroup {
|
||
// 按 activity_type 分组
|
||
typeGroups := make(map[string][]*models.AssetRegistry)
|
||
for _, reg := range registries {
|
||
if reg.ActivityType != nil && *reg.ActivityType != "" {
|
||
typeGroups[*reg.ActivityType] = append(typeGroups[*reg.ActivityType], reg)
|
||
}
|
||
}
|
||
|
||
// 构建 AssetGroup items
|
||
allItems := make([]*pb.AssetItem, 0)
|
||
for activityType, regs := range typeGroups {
|
||
// 排序:按创建时间降序
|
||
sort.Slice(regs, func(i, j int) bool {
|
||
return regs[i].CreatedAt > regs[j].CreatedAt
|
||
})
|
||
|
||
// 截取前3条(点赞数最高的3个)
|
||
displayRegs := regs
|
||
if len(regs) > HomePageSize {
|
||
displayRegs = regs[:HomePageSize]
|
||
}
|
||
|
||
// 获取资产详情
|
||
items := s.buildAssetItemsFromRegistries(displayRegs, models.AssetTypeActivity)
|
||
for _, item := range items {
|
||
item.Category = activityType
|
||
}
|
||
allItems = append(allItems, items...)
|
||
}
|
||
|
||
// 计算 total_count 和 has_more
|
||
totalCount := int32(len(registries))
|
||
hasMore := len(registries) > HomePageSize
|
||
|
||
return &pb.AssetGroup{
|
||
Type: models.AssetTypeActivity,
|
||
Category: "",
|
||
CategoryName: CategoryNameActivity,
|
||
Items: allItems,
|
||
TotalCount: totalCount,
|
||
HasMore: hasMore,
|
||
}
|
||
}
|
||
|
||
// buildAssetItemsFromRegistries 从索引记录构建资产项(使用批量查询优化)
|
||
func (s *starbookService) buildAssetItemsFromRegistries(registries []*models.AssetRegistry, assetType string) []*pb.AssetItem {
|
||
if len(registries) == 0 {
|
||
return []*pb.AssetItem{}
|
||
}
|
||
|
||
items := make([]*pb.AssetItem, 0, len(registries))
|
||
|
||
// 收集所有 asset IDs
|
||
assetIDs := make([]int64, 0, len(registries))
|
||
for _, reg := range registries {
|
||
assetIDs = append(assetIDs, reg.AssetID)
|
||
}
|
||
|
||
// 批量查询资产信息(替代 N+1 查询)
|
||
var assetCoverMap map[int64]string // assetID -> coverURL
|
||
var assetNameMap map[int64]string // assetID -> name
|
||
var categoryMap map[int64]string // assetID -> category
|
||
|
||
switch assetType {
|
||
case models.AssetTypeRegular:
|
||
assets, err := s.assetRepo.GetByIDs(assetIDs)
|
||
if err == nil && len(assets) > 0 {
|
||
assetCoverMap = make(map[int64]string)
|
||
assetNameMap = make(map[int64]string)
|
||
for _, asset := range assets {
|
||
assetCoverMap[asset.ID] = asset.CoverURL
|
||
assetNameMap[asset.ID] = asset.Name
|
||
}
|
||
}
|
||
case models.AssetTypeCollection:
|
||
colAssets, err := s.collectionRepo.GetByAssetIDs(assetIDs)
|
||
if err == nil && len(colAssets) > 0 {
|
||
assetCoverMap = make(map[int64]string)
|
||
assetNameMap = make(map[int64]string)
|
||
categoryMap = make(map[int64]string)
|
||
for _, colAsset := range colAssets {
|
||
assetCoverMap[colAsset.AssetID] = colAsset.CoverURL
|
||
assetNameMap[colAsset.AssetID] = colAsset.Name
|
||
if colAsset.Category != "" {
|
||
categoryMap[colAsset.AssetID] = colAsset.Category
|
||
}
|
||
}
|
||
}
|
||
case models.AssetTypeActivity:
|
||
actAssets, err := s.activityRepo.GetByAssetIDs(assetIDs)
|
||
if err == nil && len(actAssets) > 0 {
|
||
assetCoverMap = make(map[int64]string)
|
||
assetNameMap = make(map[int64]string)
|
||
categoryMap = make(map[int64]string)
|
||
for _, actAsset := range actAssets {
|
||
assetCoverMap[actAsset.AssetID] = actAsset.CoverURL
|
||
assetNameMap[actAsset.AssetID] = actAsset.Name
|
||
if actAsset.ActivityType != "" {
|
||
categoryMap[actAsset.AssetID] = actAsset.ActivityType
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构建 items
|
||
for _, reg := range registries {
|
||
item := &pb.AssetItem{
|
||
AssetId: reg.AssetID,
|
||
LikeCount: reg.LikeCount,
|
||
CreatedAt: reg.CreatedAt,
|
||
Category: CastloveCategory,
|
||
Grade: 0,
|
||
DisplayStatus: reg.DisplayStatus,
|
||
}
|
||
|
||
// grade 处理
|
||
if assetType == models.AssetTypeRegular && reg.Grade != nil {
|
||
item.Grade = *reg.Grade
|
||
}
|
||
|
||
// 填充资产信息
|
||
if name, ok := assetNameMap[reg.AssetID]; ok {
|
||
item.Name = name
|
||
}
|
||
if coverURL, ok := assetCoverMap[reg.AssetID]; ok {
|
||
item.CoverUrlSigned = coverURL // 直接返回原始 URL
|
||
}
|
||
if cat, ok := categoryMap[reg.AssetID]; ok {
|
||
item.Category = cat
|
||
}
|
||
|
||
items = append(items, item)
|
||
}
|
||
|
||
return items
|
||
}
|
||
|
||
// GetStarbookItems 获取星册藏品列表(分页)
|
||
func (s *starbookService) GetStarbookItems(req *pb.GetStarbookItemsRequest, ownerUID, starID int64) (*pb.GetStarbookItemsResponse, error) {
|
||
// 参数验证
|
||
if req.Type == "" {
|
||
return nil, appErrors.ErrInvalidAssetType
|
||
}
|
||
|
||
assetType := req.Type
|
||
page := req.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := req.PageSize
|
||
if pageSize <= 0 {
|
||
pageSize = 20
|
||
}
|
||
offset := (page - 1) * pageSize
|
||
|
||
var registries []*models.AssetRegistry
|
||
var totalCount int64
|
||
var err error
|
||
|
||
switch assetType {
|
||
case models.AssetTypeRegular:
|
||
grade := req.Grade
|
||
if grade <= 0 {
|
||
grade = 1
|
||
}
|
||
registries, err = s.registryRepo.GetByOwnerAndTypeAndGrade(ownerUID, starID, assetType, grade, int(pageSize), int(offset))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
totalCount, err = s.registryRepo.CountByOwnerAndTypeAndGrade(ownerUID, starID, assetType, grade)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
case models.AssetTypeCollection:
|
||
category := req.Category
|
||
if category == "" {
|
||
category = CastloveCategory
|
||
}
|
||
registries, err = s.registryRepo.GetByOwnerAndTypeAndCategory(ownerUID, starID, assetType, category, int(pageSize), int(offset))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
totalCount, err = s.registryRepo.CountByOwnerAndTypeAndCategory(ownerUID, starID, assetType, category)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
case models.AssetTypeActivity:
|
||
category := req.Category
|
||
if category == "" {
|
||
category = CastloveCategory
|
||
}
|
||
registries, err = s.registryRepo.GetByOwnerAndTypeAndCategory(ownerUID, starID, assetType, category, int(pageSize), int(offset))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
totalCount, err = s.registryRepo.CountByOwnerAndTypeAndCategory(ownerUID, starID, assetType, category)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
default:
|
||
return nil, appErrors.ErrInvalidAssetType
|
||
}
|
||
|
||
// 构建 items
|
||
items := s.buildAssetItemsFromRegistries(registries, assetType)
|
||
|
||
hasMore := int64(page*pageSize) < totalCount
|
||
|
||
return &pb.GetStarbookItemsResponse{
|
||
Data: &pb.AssetListData{
|
||
Items: items,
|
||
Total: totalCount,
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
HasMore: hasMore,
|
||
},
|
||
}, nil
|
||
}
|
||
|