chore(backend): remove starbookService + proto (config 4.2 cleanup)

- 删 services/starbookService 目录 (Phase 2 决策, owner 已迁 assetService);
- 删 backend/proto/starbook.proto (proto 源, 不再被任何 service 引用)。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zerosaturation 2026-07-24 14:04:37 +08:00
parent 7ff2d5cdc4
commit f46464fdf9
9 changed files with 0 additions and 2081 deletions

View File

@ -1,100 +0,0 @@
syntax = "proto3";
package topfans.starbook;
option go_package = "github.com/topfans/backend/pkg/proto/starbook;starbook";
import "proto/common.proto";
import "google/api/annotations.proto";
// - /
service StarbookService {
//
rpc GetStarbookHome(GetStarbookHomeRequest) returns (GetStarbookHomeResponse) {
option (google.api.http) = {
get: "/api/v1/starbook/home"
};
}
//
rpc GetStarbookItems(GetStarbookItemsRequest) returns (GetStarbookItemsResponse) {
option (google.api.http) = {
get: "/api/v1/starbook/items"
};
}
}
// ==================== ====================
//
message GetStarbookHomeRequest {
}
//
message GetStarbookHomeResponse {
topfans.common.BaseResponse base = 1;
StarbookHomeData data = 2;
}
//
message StarbookHomeData {
repeated AssetGroup groups = 1;
}
//
message AssetGroup {
string type = 1; // 'regular' / 'collection' / 'activity'
string category = 2; // 'castlove'(regular) / collection_category / activity_type
string category_name = 3;
// regular 使 grades collection/activity 使 flat items
repeated GradeSection grades = 4; // regular
repeated AssetItem items = 5; // collection / activity
int32 total_count = 6;
bool has_more = 7;
}
// regular 使
message GradeSection {
int32 grade = 1; // 1/2/3/4/5...
repeated AssetItem items = 2;
int32 total_count = 3;
bool has_more = 4;
}
//
message AssetItem {
int64 asset_id = 1;
string name = 2;
string cover_url_signed = 3; // URL
int32 like_count = 4;
int64 created_at = 5;
string category = 6; // regular: 'castlove' / collection: category / activity: activity_type
int32 grade = 7; // regular 1/2/3... 0
int32 display_status = 8; // 0=, 1=
}
// ==================== ====================
//
message GetStarbookItemsRequest {
string type = 1; // 'regular' / 'collection' / 'activity'
string category = 2; // regular 'castlove'
int32 grade = 3; // regular 1/2/3...
int32 page = 4; // 1
int32 page_size = 5; // 20
}
//
message GetStarbookItemsResponse {
topfans.common.BaseResponse base = 1;
AssetListData data = 2;
}
//
message AssetListData {
repeated AssetItem items = 1;
int64 total = 2;
int32 page = 3;
int32 page_size = 4;
bool has_more = 5;
}

View File

@ -1,203 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"strconv"
"syscall"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"dubbo.apache.org/dubbo-go/v3/protocol"
"dubbo.apache.org/dubbo-go/v3/server"
"github.com/topfans/backend/pkg/database"
"github.com/topfans/backend/pkg/health"
"github.com/topfans/backend/pkg/logger"
"github.com/topfans/backend/pkg/models"
pb "github.com/topfans/backend/pkg/proto/starbook"
assetRepo "github.com/topfans/backend/services/assetService/repository"
"github.com/topfans/backend/services/starbookService/provider"
"github.com/topfans/backend/services/starbookService/repository"
"github.com/topfans/backend/services/starbookService/service"
)
var (
port = flag.Int("port", getEnvInt("PORT", 20005), "Dubbo service port")
dbHost = flag.String("db-host", getEnv("DB_HOST", "localhost"), "Database host")
dbPort = flag.Int("db-port", getEnvInt("DB_PORT", 5432), "Database port")
dbUser = flag.String("db-user", getEnv("DB_USER", "postgres"), "Database user")
dbPassword = flag.String("db-password", getEnv("DB_PASSWORD", ""), "Database password")
dbName = flag.String("db-name", getEnv("DB_NAME", "top-fans"), "Database name")
healthHandler *health.Handler
)
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func getEnvInt(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return fallback
}
func main() {
flag.Parse()
// 初始化日志(必须在最前面)
env := os.Getenv("ENV")
if env == "" {
env = "development"
}
if err := logger.Init(logger.Config{
ServiceName: "starbook-service",
Environment: env,
LogLevel: os.Getenv("LOG_LEVEL"),
}); err != nil {
panic(fmt.Sprintf("Failed to initialize logger: %v", err))
}
defer logger.Sync()
logger.Sugar.Info("Starting Starbook Service...")
// 初始化数据库
if err := initDatabase(); err != nil {
logger.Sugar.Fatalf("Failed to initialize database: %v", err)
}
// 自动迁移数据库表
if err := autoMigrate(); err != nil {
logger.Sugar.Fatalf("Failed to migrate database: %v", err)
}
// 初始化 Repository
registryRepo := repository.NewAssetRegistryRepository(database.GetDB())
collectionRepo := repository.NewCollectionRepository(database.GetDB())
activityRepo := repository.NewActivityAssetRepository(database.GetDB())
assetRepository := assetRepo.NewAssetRepository(database.GetDB())
// 初始化 Service
starbookService := service.NewStarbookService(
database.GetDB(),
registryRepo,
assetRepository,
collectionRepo,
activityRepo,
)
// 初始化 Provider
starbookProvider := provider.NewStarbookProvider(starbookService)
// 初始化 Dubbo-go 服务器
if err := initDubboService(starbookProvider); err != nil {
logger.Sugar.Fatalf("Failed to initialize Dubbo service: %v", err)
}
// 等待信号(优雅关闭)
logger.Sugar.Info("Starbook service started successfully. Press Ctrl+C to exit.")
gracefulShutdown()
}
// initDatabase 初始化数据库连接
func initDatabase() error {
config := database.Config{
Host: *dbHost,
Port: *dbPort,
User: *dbUser,
Password: *dbPassword,
DBName: *dbName,
SSLMode: "disable",
TimeZone: "Asia/Shanghai",
}
return database.Init(config)
}
// autoMigrate 自动迁移数据库表
func autoMigrate() error {
db := database.GetDB()
if db == nil {
return fmt.Errorf("database is not initialized")
}
// 迁移星册相关的表
tables := []interface{}{
&models.CollectionAsset{},
&models.ActivityAsset{},
&models.AssetRegistry{},
}
for _, table := range tables {
if err := db.AutoMigrate(table); err != nil {
return fmt.Errorf("failed to migrate table: %w", err)
}
}
logger.Sugar.Info("Database migration completed successfully")
return nil
}
// initDubboService 初始化 Dubbo 服务
func initDubboService(starbookProvider *provider.StarbookProvider) error {
// 启动健康检查 HTTP 服务器
healthPort := *port + 1000 // e.g., 20005 -> 21005
healthHandler = health.NewHandler("starbook-service", healthPort)
healthHandler.Start()
// 创建 Dubbo Server
srv, err := server.NewServer(
server.WithServerProtocol(
protocol.WithPort(*port),
protocol.WithTriple(),
),
)
if err != nil {
return fmt.Errorf("failed to create Dubbo server: %w", err)
}
// 注册服务
if err := pb.RegisterStarbookServiceHandler(srv, starbookProvider); err != nil {
return fmt.Errorf("failed to register StarbookService handler: %w", err)
}
logger.Sugar.Infof("Dubbo-go provider registered successfully, service: topfans.starbook.StarbookService, port: %d", *port)
// 在后台启动 Dubbo 服务器
go func() {
if err := srv.Serve(); err != nil {
logger.Sugar.Fatalf("Failed to serve Dubbo: %v", err)
}
}()
return nil
}
// gracefulShutdown 优雅关闭
func gracefulShutdown() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Sugar.Info("Shutting down Starbook Service...")
// 关闭健康检查服务器
if healthHandler != nil {
healthHandler.Stop()
}
// 关闭数据库连接
if err := database.Close(); err != nil {
logger.Sugar.Errorf("Error closing database: %v", err)
}
logger.Sugar.Info("Starbook Service stopped")
}

View File

@ -1,220 +0,0 @@
package provider
import (
"context"
"fmt"
"strconv"
"time"
"dubbo.apache.org/dubbo-go/v3/common/constant"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/logger"
pb "github.com/topfans/backend/pkg/proto/starbook"
pbCommon "github.com/topfans/backend/pkg/proto/common"
"github.com/topfans/backend/services/starbookService/service"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
)
// StarbookProvider 星册服务Provider实现
// 实现 Triple 协议生成的 StarbookServiceHandler 接口
type StarbookProvider struct {
starbookService service.StarbookService
}
// 确保 StarbookProvider 实现了 StarbookServiceHandler 接口
var _ pb.StarbookServiceHandler = (*StarbookProvider)(nil)
// NewStarbookProvider 创建星册服务Provider实例
func NewStarbookProvider(starbookService service.StarbookService) *StarbookProvider {
return &StarbookProvider{
starbookService: starbookService,
}
}
// GetStarbookHome 获取星册首页
func (p *StarbookProvider) GetStarbookHome(ctx context.Context, req *pb.GetStarbookHomeRequest) (*pb.GetStarbookHomeResponse, error) {
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
return &pb.GetStarbookHomeResponse{
Base: &pbCommon.BaseResponse{
Code: uint32(codes.Unauthenticated),
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
resp, err := p.starbookService.GetStarbookHome(userID, starID)
if err != nil {
logger.Logger.Error("GetStarbookHome failed",
zap.Error(err),
)
return &pb.GetStarbookHomeResponse{
Base: &pbCommon.BaseResponse{
Code: uint32(toStatusCode(err)),
Message: err.Error(),
Timestamp: time.Now().UnixMilli(),
},
}, err
}
return resp, nil
}
// GetStarbookItems 获取星册藏品列表
func (p *StarbookProvider) GetStarbookItems(ctx context.Context, req *pb.GetStarbookItemsRequest) (*pb.GetStarbookItemsResponse, error) {
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
return &pb.GetStarbookItemsResponse{
Base: &pbCommon.BaseResponse{
Code: uint32(codes.Unauthenticated),
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
resp, err := p.starbookService.GetStarbookItems(req, userID, starID)
if err != nil {
logger.Logger.Error("GetStarbookItems failed",
zap.Error(err),
)
return &pb.GetStarbookItemsResponse{
Base: &pbCommon.BaseResponse{
Code: uint32(toStatusCode(err)),
Message: err.Error(),
Timestamp: time.Now().UnixMilli(),
},
}, err
}
return resp, nil
}
// extractUserInfoFromDubboAttachments 从 Dubbo attachments 中提取用户信息
// 网关调用:网关已验证 Token 并将 user_id 和 star_id 通过 attachments 传递
func extractUserInfoFromDubboAttachments(ctx context.Context) (int64, int64, error) {
logger.Logger.Info("=== extractUserInfoFromDubboAttachments called ===")
// 使用 constant.AttachmentKey 获取 Dubbo attachments
attachments := ctx.Value(constant.AttachmentKey)
logger.Logger.Info("ctx.Value(constant.AttachmentKey) result",
zap.Any("attachments", attachments),
zap.String("type", fmt.Sprintf("%T", attachments)),
)
if attachments != nil {
logger.Logger.Info("Attachments found in context")
if attMap, ok := attachments.(map[string]interface{}); ok {
logger.Logger.Info("Attachments is map[string]interface{}",
zap.Int("map_size", len(attMap)))
userID, starID := extractUserInfoFromMap(attMap)
if userID > 0 && starID > 0 {
logger.Logger.Info("Successfully extracted user info",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
)
return userID, starID, nil
}
logger.Logger.Warn("Extracted zero user_id or star_id",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
)
} else {
logger.Logger.Warn("Attachments is not map[string]interface{}",
zap.String("actual_type", fmt.Sprintf("%T", attachments)),
zap.Any("value", attachments),
)
}
} else {
logger.Logger.Warn("No attachments found in context")
// 尝试打印所有 context 的值
logger.Logger.Warn("Context type", zap.String("type", fmt.Sprintf("%T", ctx)))
}
return 0, 0, fmt.Errorf("user info not found in Dubbo attachments (expected user_id and star_id from gateway)")
}
// getContextKeys 获取 context 中所有的 key
func getContextKeys(ctx context.Context) []string {
keys := make([]string, 0)
// 直接打印 ctx 类型
logger.Logger.Debug("Context type", zap.String("type", fmt.Sprintf("%T", ctx)))
return keys
}
// getContextValues 遍历 context 中的一些已知值
func getContextValues(ctx context.Context) map[string]interface{} {
result := make(map[string]interface{})
// 检查 constant.AttachmentKey
if v := ctx.Value(constant.AttachmentKey); v != nil {
result["AttachmentKey"] = v
}
return result
}
// extractUserInfoFromMap 从 map 中提取 user_id 和 star_id
// 支持多种类型int64, float64, string
func extractUserInfoFromMap(attMap map[string]interface{}) (int64, int64) {
var userID, starID int64
// 打印所有 key 和 value
for k, v := range attMap {
logger.Logger.Debug("Attachment map entry",
zap.String("key", k),
zap.Any("value", v),
zap.String("type", fmt.Sprintf("%T", v)),
)
}
// 提取 user_id
if v, ok := attMap["user_id"]; ok {
userID = parseIntValue(v)
}
// 提取 star_id
if v, ok := attMap["star_id"]; ok {
starID = parseIntValue(v)
}
return userID, starID
}
// parseIntValue 解析不同类型的整数值
func parseIntValue(v interface{}) int64 {
switch val := v.(type) {
case int64:
return val
case int:
return int64(val)
case float64:
return int64(val)
case string:
if i, err := strconv.ParseInt(val, 10, 64); err == nil {
return i
}
case []string:
if len(val) > 0 {
if i, err := strconv.ParseInt(val[0], 10, 64); err == nil {
return i
}
}
case []interface{}:
if len(val) > 0 {
if s, ok := val[0].(string); ok {
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return i
}
}
}
}
return 0
}
// toStatusCode 将错误转换为 google.rpc.Code
// 包装 appErrors.ToGRPCCode 以便该 provider 内调用更简洁
func toStatusCode(err error) codes.Code {
return appErrors.ToGRPCCode(err)
}

View File

@ -1,247 +0,0 @@
package repository
import (
"errors"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/models"
"gorm.io/gorm"
)
// ActivityAssetRepository 活动藏品Repository接口
type ActivityAssetRepository interface {
// Create 创建活动藏品
Create(asset *models.ActivityAsset) error
// GetByID 根据ID查询
GetByID(id int64) (*models.ActivityAsset, error)
// GetByAssetID 根据asset_id查询
GetByAssetID(assetID int64) (*models.ActivityAsset, error)
// GetByAssetIDs 批量查询
GetByAssetIDs(assetIDs []int64) ([]*models.ActivityAsset, error)
// GetByOwner 查询用户的活动藏品列表
GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.ActivityAsset, error)
// GetByOwnerAndActivityType 查询用户指定活动类型的活动藏品
GetByOwnerAndActivityType(ownerUID, starID int64, activityType string, limit, offset int) ([]*models.ActivityAsset, error)
// GetByOwnerAndActivityID 查询用户指定活动的活动藏品
GetByOwnerAndActivityID(ownerUID, starID int64, activityID int64, limit, offset int) ([]*models.ActivityAsset, error)
// CountByOwner 统计用户的活动藏品数量
CountByOwner(ownerUID, starID int64) (int64, error)
// CountByOwnerAndActivityType 统计用户指定活动类型的活动藏品数量
CountByOwnerAndActivityType(ownerUID, starID int64, activityType string) (int64, error)
// UpdateLikeCount 更新点赞数
UpdateLikeCount(id int64, likeCount int32) error
// IncrementLikeCount 增加点赞数
IncrementLikeCount(id int64) error
// DecrementLikeCount 减少点赞数
DecrementLikeCount(id int64) error
}
// activityAssetRepository 活动藏品Repository实现
type activityAssetRepository struct {
db *gorm.DB
}
// NewActivityAssetRepository 创建活动藏品Repository实例
func NewActivityAssetRepository(db *gorm.DB) ActivityAssetRepository {
return &activityAssetRepository{db: db}
}
// Create 创建活动藏品
func (r *activityAssetRepository) Create(asset *models.ActivityAsset) error {
if asset == nil {
return errors.New("activity asset cannot be nil")
}
if asset.OwnerUID <= 0 {
return errors.New("owner_uid must be greater than 0")
}
if asset.StarID <= 0 {
return errors.New("star_id must be greater than 0")
}
return r.db.Create(asset).Error
}
// GetByID 根据ID查询
func (r *activityAssetRepository) GetByID(id int64) (*models.ActivityAsset, error) {
if id <= 0 {
return nil, errors.New("id must be greater than 0")
}
var asset models.ActivityAsset
if err := r.db.Where("id = ?", id).First(&asset).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrActivityAssetNotFound
}
return nil, err
}
return &asset, nil
}
// GetByAssetID 根据asset_id查询
func (r *activityAssetRepository) GetByAssetID(assetID int64) (*models.ActivityAsset, error) {
if assetID <= 0 {
return nil, errors.New("asset_id must be greater than 0")
}
var asset models.ActivityAsset
if err := r.db.Where("asset_id = ?", assetID).First(&asset).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrActivityAssetNotFound
}
return nil, err
}
return &asset, nil
}
// GetByAssetIDs 批量查询
func (r *activityAssetRepository) GetByAssetIDs(assetIDs []int64) ([]*models.ActivityAsset, error) {
if len(assetIDs) == 0 {
return []*models.ActivityAsset{}, nil
}
var assets []*models.ActivityAsset
if err := r.db.Where("asset_id IN ?", assetIDs).Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// GetByOwner 查询用户的活动藏品列表
func (r *activityAssetRepository) GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.ActivityAsset, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var assets []*models.ActivityAsset
query := r.db.Where("owner_uid = ? AND star_id = ?", ownerUID, starID).
Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// GetByOwnerAndActivityType 查询用户指定活动类型的活动藏品
func (r *activityAssetRepository) GetByOwnerAndActivityType(ownerUID, starID int64, activityType string, limit, offset int) ([]*models.ActivityAsset, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var assets []*models.ActivityAsset
query := r.db.Where("owner_uid = ? AND star_id = ? AND activity_type = ?", ownerUID, starID, activityType).
Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// GetByOwnerAndActivityID 查询用户指定活动的活动藏品
func (r *activityAssetRepository) GetByOwnerAndActivityID(ownerUID, starID int64, activityID int64, limit, offset int) ([]*models.ActivityAsset, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var assets []*models.ActivityAsset
query := r.db.Where("owner_uid = ? AND star_id = ? AND activity_id = ?", ownerUID, starID, activityID).
Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// CountByOwner 统计用户的活动藏品数量
func (r *activityAssetRepository) CountByOwner(ownerUID, starID int64) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.ActivityAsset{}).
Where("owner_uid = ? AND star_id = ?", ownerUID, starID).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndActivityType 统计用户指定活动类型的活动藏品数量
func (r *activityAssetRepository) CountByOwnerAndActivityType(ownerUID, starID int64, activityType string) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.ActivityAsset{}).
Where("owner_uid = ? AND star_id = ? AND activity_type = ?", ownerUID, starID, activityType).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// UpdateLikeCount 更新点赞数
func (r *activityAssetRepository) UpdateLikeCount(id int64, likeCount int32) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.ActivityAsset{}).
Where("id = ?", id).
Update("like_count", likeCount).Error
}
// IncrementLikeCount 增加点赞数
func (r *activityAssetRepository) IncrementLikeCount(id int64) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.ActivityAsset{}).
Where("id = ?", id).
UpdateColumn("like_count", gorm.Expr("like_count + ?", 1)).Error
}
// DecrementLikeCount 减少点赞数
func (r *activityAssetRepository) DecrementLikeCount(id int64) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.ActivityAsset{}).
Where("id = ? AND like_count > ?", id, 0).
UpdateColumn("like_count", gorm.Expr("like_count - ?", 1)).Error
}

View File

@ -1,387 +0,0 @@
package repository
import (
"errors"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/models"
"gorm.io/gorm"
)
// AssetRegistryRepository 资产统一索引Repository接口
type AssetRegistryRepository interface {
// Create 创建索引记录
Create(registry *models.AssetRegistry) error
// GetByID 根据ID查询
GetByID(id int64) (*models.AssetRegistry, error)
// GetByAssetID 根据asset_id查询
GetByAssetID(assetID int64) (*models.AssetRegistry, error)
// GetByAssetTypeAndID 根据类型和asset_id查询
GetByAssetTypeAndID(assetType string, assetID int64) (*models.AssetRegistry, error)
// GetByOwner 查询用户的所有索引记录
GetByOwner(ownerUID, starID int64) ([]*models.AssetRegistry, error)
// GetByOwnerAndType 查询用户指定类型的索引记录
GetByOwnerAndType(ownerUID, starID int64, assetType string, limit, offset int) ([]*models.AssetRegistry, error)
// GetByOwnerAndTypeAndGrade 查询用户指定类型和等级的索引记录
GetByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32, limit, offset int) ([]*models.AssetRegistry, error)
// GetByOwnerAndTypeAndCategory 查询用户指定类型和分类的索引记录
GetByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string, limit, offset int) ([]*models.AssetRegistry, error)
// GetByOwnerAndTypeAndActivity 查询用户指定类型和活动的索引记录
GetByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64, limit, offset int) ([]*models.AssetRegistry, error)
// CountByOwner 统计用户的索引记录数量
CountByOwner(ownerUID, starID int64) (int64, error)
// CountByOwnerAndType 统计用户指定类型的索引记录数量
CountByOwnerAndType(ownerUID, starID int64, assetType string) (int64, error)
// CountByOwnerAndTypeAndGrade 统计用户指定类型和等级的索引记录数量
CountByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32) (int64, error)
// CountByOwnerAndTypeAndCategory 统计用户指定类型和分类的索引记录数量
CountByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string) (int64, error)
// CountByOwnerAndTypeAndActivity 统计用户指定类型和活动的索引记录数量
CountByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64) (int64, error)
// UpdateLikeCount 更新点赞数
UpdateLikeCount(assetID int64, likeCount int32) error
// UpdateGrade 更新等级
UpdateGrade(assetID int64, grade int32) error
// Delete 删除索引记录
Delete(assetID int64) error
// DeleteByAssetType 删除指定类型的索引记录
DeleteByAssetType(assetType string, assetID int64) error
}
// assetRegistryRepository 资产统一索引Repository实现
type assetRegistryRepository struct {
db *gorm.DB
}
// NewAssetRegistryRepository 创建资产统一索引Repository实例
func NewAssetRegistryRepository(db *gorm.DB) AssetRegistryRepository {
return &assetRegistryRepository{db: db}
}
// Create 创建索引记录
func (r *assetRegistryRepository) Create(registry *models.AssetRegistry) error {
if registry == nil {
return errors.New("registry cannot be nil")
}
if registry.OwnerUID <= 0 {
return errors.New("owner_uid must be greater than 0")
}
if registry.StarID <= 0 {
return errors.New("star_id must be greater than 0")
}
return r.db.Create(registry).Error
}
// GetByID 根据ID查询
func (r *assetRegistryRepository) GetByID(id int64) (*models.AssetRegistry, error) {
if id <= 0 {
return nil, errors.New("id must be greater than 0")
}
var registry models.AssetRegistry
if err := r.db.Where("id = ?", id).First(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, nil
}
// GetByAssetID 根据asset_id查询
func (r *assetRegistryRepository) GetByAssetID(assetID int64) (*models.AssetRegistry, error) {
if assetID <= 0 {
return nil, errors.New("asset_id must be greater than 0")
}
var registry models.AssetRegistry
if err := r.db.Where("asset_id = ?", assetID).First(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, nil
}
// GetByAssetTypeAndID 根据类型和asset_id查询
func (r *assetRegistryRepository) GetByAssetTypeAndID(assetType string, assetID int64) (*models.AssetRegistry, error) {
if assetType == "" {
return nil, errors.New("asset_type must not be empty")
}
if assetID <= 0 {
return nil, errors.New("asset_id must be greater than 0")
}
var registry models.AssetRegistry
if err := r.db.Where("asset_type = ? AND asset_id = ?", assetType, assetID).First(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, nil
}
// GetByOwner 查询用户的所有索引记录
func (r *assetRegistryRepository) GetByOwner(ownerUID, starID int64) ([]*models.AssetRegistry, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var registries []*models.AssetRegistry
if err := r.db.
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ?", ownerUID, starID).
Order("asset_registry.created_at DESC").
Find(&registries).Error; err != nil {
return nil, err
}
return registries, nil
}
// GetByOwnerAndType 查询用户指定类型的索引记录
func (r *assetRegistryRepository) GetByOwnerAndType(ownerUID, starID int64, assetType string, limit, offset int) ([]*models.AssetRegistry, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var registries []*models.AssetRegistry
query := r.db.
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ?", ownerUID, starID, assetType).
Order("asset_registry.created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&registries).Error; err != nil {
return nil, err
}
return registries, nil
}
// GetByOwnerAndTypeAndGrade 查询用户指定类型和等级的索引记录
func (r *assetRegistryRepository) GetByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32, limit, offset int) ([]*models.AssetRegistry, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var registries []*models.AssetRegistry
query := r.db.
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.grade = ?", ownerUID, starID, assetType, grade).
Order("asset_registry.created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&registries).Error; err != nil {
return nil, err
}
return registries, nil
}
// GetByOwnerAndTypeAndCategory 查询用户指定类型和分类的索引记录
func (r *assetRegistryRepository) GetByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string, limit, offset int) ([]*models.AssetRegistry, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var registries []*models.AssetRegistry
query := r.db.
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.collection_category = ?", ownerUID, starID, assetType, category).
Order("asset_registry.created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&registries).Error; err != nil {
return nil, err
}
return registries, nil
}
// GetByOwnerAndTypeAndActivity 查询用户指定类型和活动的索引记录
func (r *assetRegistryRepository) GetByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64, limit, offset int) ([]*models.AssetRegistry, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var registries []*models.AssetRegistry
query := r.db.
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.activity_id = ?", ownerUID, starID, assetType, activityID).
Order("asset_registry.created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&registries).Error; err != nil {
return nil, err
}
return registries, nil
}
// CountByOwner 统计用户的索引记录数量
func (r *assetRegistryRepository) CountByOwner(ownerUID, starID int64) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.AssetRegistry{}).
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ?", ownerUID, starID).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndType 统计用户指定类型的索引记录数量
func (r *assetRegistryRepository) CountByOwnerAndType(ownerUID, starID int64, assetType string) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.AssetRegistry{}).
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ?", ownerUID, starID, assetType).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndTypeAndGrade 统计用户指定类型和等级的索引记录数量
func (r *assetRegistryRepository) CountByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.AssetRegistry{}).
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.grade = ?", ownerUID, starID, assetType, grade).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndTypeAndCategory 统计用户指定类型和分类的索引记录数量
func (r *assetRegistryRepository) CountByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.AssetRegistry{}).
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.collection_category = ?", ownerUID, starID, assetType, category).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndTypeAndActivity 统计用户指定类型和活动的索引记录数量
func (r *assetRegistryRepository) CountByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.AssetRegistry{}).
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.activity_id = ?", ownerUID, starID, assetType, activityID).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// UpdateLikeCount 更新点赞数
func (r *assetRegistryRepository) UpdateLikeCount(assetID int64, likeCount int32) error {
if assetID <= 0 {
return errors.New("asset_id must be greater than 0")
}
return r.db.Model(&models.AssetRegistry{}).
Where("asset_id = ?", assetID).
Update("like_count", likeCount).Error
}
// UpdateGrade 更新等级
func (r *assetRegistryRepository) UpdateGrade(assetID int64, grade int32) error {
if assetID <= 0 {
return errors.New("asset_id must be greater than 0")
}
return r.db.Model(&models.AssetRegistry{}).
Where("asset_id = ?", assetID).
Update("grade", grade).Error
}
// Delete 删除索引记录
func (r *assetRegistryRepository) Delete(assetID int64) error {
if assetID <= 0 {
return errors.New("asset_id must be greater than 0")
}
return r.db.Where("asset_id = ?", assetID).Delete(&models.AssetRegistry{}).Error
}
// DeleteByAssetType 删除指定类型的索引记录
func (r *assetRegistryRepository) DeleteByAssetType(assetType string, assetID int64) error {
if assetType == "" {
return errors.New("asset_type must not be empty")
}
if assetID <= 0 {
return errors.New("asset_id must be greater than 0")
}
return r.db.Where("asset_type = ? AND asset_id = ?", assetType, assetID).
Delete(&models.AssetRegistry{}).Error
}

View File

@ -1,221 +0,0 @@
package repository
import (
"errors"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/models"
"gorm.io/gorm"
)
// CollectionRepository 典藏藏品Repository接口
type CollectionRepository interface {
// Create 创建典藏藏品
Create(asset *models.CollectionAsset) error
// GetByID 根据ID查询
GetByID(id int64) (*models.CollectionAsset, error)
// GetByAssetID 根据asset_id查询
GetByAssetID(assetID int64) (*models.CollectionAsset, error)
// GetByAssetIDs 批量查询
GetByAssetIDs(assetIDs []int64) ([]*models.CollectionAsset, error)
// GetByOwner 查询用户的典藏藏品列表
GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.CollectionAsset, error)
// GetByOwnerAndCategory 查询用户指定分类的典藏藏品
GetByOwnerAndCategory(ownerUID, starID int64, category string, limit, offset int) ([]*models.CollectionAsset, error)
// CountByOwner 统计用户的典藏藏品数量
CountByOwner(ownerUID, starID int64) (int64, error)
// CountByOwnerAndCategory 统计用户指定分类的典藏藏品数量
CountByOwnerAndCategory(ownerUID, starID int64, category string) (int64, error)
// UpdateLikeCount 更新点赞数
UpdateLikeCount(id int64, likeCount int32) error
// IncrementLikeCount 增加点赞数
IncrementLikeCount(id int64) error
// DecrementLikeCount 减少点赞数
DecrementLikeCount(id int64) error
}
// collectionRepository 典藏藏品Repository实现
type collectionRepository struct {
db *gorm.DB
}
// NewCollectionRepository 创建典藏藏品Repository实例
func NewCollectionRepository(db *gorm.DB) CollectionRepository {
return &collectionRepository{db: db}
}
// Create 创建典藏藏品
func (r *collectionRepository) Create(asset *models.CollectionAsset) error {
if asset == nil {
return errors.New("collection asset cannot be nil")
}
if asset.OwnerUID <= 0 {
return errors.New("owner_uid must be greater than 0")
}
if asset.StarID <= 0 {
return errors.New("star_id must be greater than 0")
}
return r.db.Create(asset).Error
}
// GetByID 根据ID查询
func (r *collectionRepository) GetByID(id int64) (*models.CollectionAsset, error) {
if id <= 0 {
return nil, errors.New("id must be greater than 0")
}
var asset models.CollectionAsset
if err := r.db.Where("id = ?", id).First(&asset).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrCollectionAssetNotFound
}
return nil, err
}
return &asset, nil
}
// GetByAssetID 根据asset_id查询
func (r *collectionRepository) GetByAssetID(assetID int64) (*models.CollectionAsset, error) {
if assetID <= 0 {
return nil, errors.New("asset_id must be greater than 0")
}
var asset models.CollectionAsset
if err := r.db.Where("asset_id = ?", assetID).First(&asset).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrCollectionAssetNotFound
}
return nil, err
}
return &asset, nil
}
// GetByAssetIDs 批量查询
func (r *collectionRepository) GetByAssetIDs(assetIDs []int64) ([]*models.CollectionAsset, error) {
if len(assetIDs) == 0 {
return []*models.CollectionAsset{}, nil
}
var assets []*models.CollectionAsset
if err := r.db.Where("asset_id IN ?", assetIDs).Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// GetByOwner 查询用户的典藏藏品列表
func (r *collectionRepository) GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.CollectionAsset, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var assets []*models.CollectionAsset
query := r.db.Where("owner_uid = ? AND star_id = ?", ownerUID, starID).
Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// GetByOwnerAndCategory 查询用户指定分类的典藏藏品
func (r *collectionRepository) GetByOwnerAndCategory(ownerUID, starID int64, category string, limit, offset int) ([]*models.CollectionAsset, error) {
if ownerUID <= 0 {
return nil, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return nil, errors.New("star_id must be greater than 0")
}
var assets []*models.CollectionAsset
query := r.db.Where("owner_uid = ? AND star_id = ? AND category = ?", ownerUID, starID, category).
Order("created_at DESC")
if limit > 0 {
query = query.Limit(limit)
}
if offset > 0 {
query = query.Offset(offset)
}
if err := query.Find(&assets).Error; err != nil {
return nil, err
}
return assets, nil
}
// CountByOwner 统计用户的典藏藏品数量
func (r *collectionRepository) CountByOwner(ownerUID, starID int64) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.CollectionAsset{}).
Where("owner_uid = ? AND star_id = ?", ownerUID, starID).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// CountByOwnerAndCategory 统计用户指定分类的典藏藏品数量
func (r *collectionRepository) CountByOwnerAndCategory(ownerUID, starID int64, category string) (int64, error) {
if ownerUID <= 0 {
return 0, errors.New("owner_uid must be greater than 0")
}
if starID <= 0 {
return 0, errors.New("star_id must be greater than 0")
}
var count int64
if err := r.db.Model(&models.CollectionAsset{}).
Where("owner_uid = ? AND star_id = ? AND category = ?", ownerUID, starID, category).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// UpdateLikeCount 更新点赞数
func (r *collectionRepository) UpdateLikeCount(id int64, likeCount int32) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.CollectionAsset{}).
Where("id = ?", id).
Update("like_count", likeCount).Error
}
// IncrementLikeCount 增加点赞数
func (r *collectionRepository) IncrementLikeCount(id int64) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.CollectionAsset{}).
Where("id = ?", id).
UpdateColumn("like_count", gorm.Expr("like_count + ?", 1)).Error
}
// DecrementLikeCount 减少点赞数
func (r *collectionRepository) DecrementLikeCount(id int64) error {
if id <= 0 {
return errors.New("id must be greater than 0")
}
return r.db.Model(&models.CollectionAsset{}).
Where("id = ? AND like_count > ?", id, 0).
UpdateColumn("like_count", gorm.Expr("like_count - ?", 1)).Error
}

View File

@ -1,132 +0,0 @@
package service
import (
"time"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/models"
"github.com/topfans/backend/services/starbookService/repository"
)
// ActivityAssetService 活动藏品服务接口
type ActivityAssetService interface {
// Create 创建活动藏品
Create(asset *models.ActivityAsset) error
// GetByID 根据ID获取
GetByID(id int64) (*models.ActivityAsset, error)
// GetByAssetID 根据asset_id获取
GetByAssetID(assetID int64) (*models.ActivityAsset, error)
// GetByOwner 获取用户的活动藏品
GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.ActivityAsset, error)
// GetByOwnerAndActivityType 获取用户指定活动类型的活动藏品
GetByOwnerAndActivityType(ownerUID, starID int64, activityType string, limit, offset int) ([]*models.ActivityAsset, error)
// GetByOwnerAndActivityID 获取用户指定活动的活动藏品
GetByOwnerAndActivityID(ownerUID, starID int64, activityID int64, limit, offset int) ([]*models.ActivityAsset, error)
// CountByOwner 统计用户的活动藏品数量
CountByOwner(ownerUID, starID int64) (int64, error)
// CountByOwnerAndActivityType 统计用户指定活动类型的活动藏品数量
CountByOwnerAndActivityType(ownerUID, starID int64, activityType string) (int64, error)
// IncrementLikeCount 增加点赞数
IncrementLikeCount(id int64) error
// DecrementLikeCount 减少点赞数
DecrementLikeCount(id int64) error
}
// activityAssetService 活动藏品服务实现
type activityAssetService struct {
activityRepo repository.ActivityAssetRepository
registryRepo repository.AssetRegistryRepository
}
// NewActivityAssetService 创建活动藏品服务实例
func NewActivityAssetService(
activityRepo repository.ActivityAssetRepository,
registryRepo repository.AssetRegistryRepository,
) ActivityAssetService {
return &activityAssetService{
activityRepo: activityRepo,
registryRepo: registryRepo,
}
}
// Create 创建活动藏品
func (s *activityAssetService) Create(asset *models.ActivityAsset) error {
if asset == nil {
return appErrors.ErrInvalidAssetStatus
}
// 创建活动藏品记录
if err := s.activityRepo.Create(asset); err != nil {
return err
}
// 同步写入 asset_registry
registry := &models.AssetRegistry{
AssetID: asset.AssetID,
AssetType: models.AssetTypeActivity,
OwnerUID: asset.OwnerUID,
StarID: asset.StarID,
ActivityID: &asset.ActivityID,
ActivityType: &asset.ActivityType,
Status: asset.Status,
LikeCount: asset.LikeCount,
CreatedAt: time.Now().UnixMilli(),
UpdatedAt: time.Now().UnixMilli(),
}
return s.registryRepo.Create(registry)
}
// GetByID 根据ID获取
func (s *activityAssetService) GetByID(id int64) (*models.ActivityAsset, error) {
return s.activityRepo.GetByID(id)
}
// GetByAssetID 根据asset_id获取
func (s *activityAssetService) GetByAssetID(assetID int64) (*models.ActivityAsset, error) {
return s.activityRepo.GetByAssetID(assetID)
}
// GetByOwner 获取用户的活动藏品
func (s *activityAssetService) GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.ActivityAsset, error) {
return s.activityRepo.GetByOwner(ownerUID, starID, limit, offset)
}
// GetByOwnerAndActivityType 获取用户指定活动类型的活动藏品
func (s *activityAssetService) GetByOwnerAndActivityType(ownerUID, starID int64, activityType string, limit, offset int) ([]*models.ActivityAsset, error) {
return s.activityRepo.GetByOwnerAndActivityType(ownerUID, starID, activityType, limit, offset)
}
// GetByOwnerAndActivityID 获取用户指定活动的活动藏品
func (s *activityAssetService) GetByOwnerAndActivityID(ownerUID, starID int64, activityID int64, limit, offset int) ([]*models.ActivityAsset, error) {
return s.activityRepo.GetByOwnerAndActivityID(ownerUID, starID, activityID, limit, offset)
}
// CountByOwner 统计用户的活动藏品数量
func (s *activityAssetService) CountByOwner(ownerUID, starID int64) (int64, error) {
return s.activityRepo.CountByOwner(ownerUID, starID)
}
// CountByOwnerAndActivityType 统计用户指定活动类型的活动藏品数量
func (s *activityAssetService) CountByOwnerAndActivityType(ownerUID, starID int64, activityType string) (int64, error) {
return s.activityRepo.CountByOwnerAndActivityType(ownerUID, starID, activityType)
}
// IncrementLikeCount 增加点赞数
func (s *activityAssetService) IncrementLikeCount(id int64) error {
return s.activityRepo.IncrementLikeCount(id)
}
// DecrementLikeCount 减少点赞数
func (s *activityAssetService) DecrementLikeCount(id int64) error {
return s.activityRepo.DecrementLikeCount(id)
}

View File

@ -1,123 +0,0 @@
package service
import (
"time"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/models"
"github.com/topfans/backend/services/starbookService/repository"
)
// CollectionService 典藏服务接口
type CollectionService interface {
// Create 创建典藏藏品
Create(asset *models.CollectionAsset) error
// GetByID 根据ID获取
GetByID(id int64) (*models.CollectionAsset, error)
// GetByAssetID 根据asset_id获取
GetByAssetID(assetID int64) (*models.CollectionAsset, error)
// GetByOwner 获取用户的典藏藏品
GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.CollectionAsset, error)
// GetByOwnerAndCategory 获取用户指定分类的典藏藏品
GetByOwnerAndCategory(ownerUID, starID int64, category string, limit, offset int) ([]*models.CollectionAsset, error)
// CountByOwner 统计用户的典藏藏品数量
CountByOwner(ownerUID, starID int64) (int64, error)
// CountByOwnerAndCategory 统计用户指定分类的典藏藏品数量
CountByOwnerAndCategory(ownerUID, starID int64, category string) (int64, error)
// IncrementLikeCount 增加点赞数
IncrementLikeCount(id int64) error
// DecrementLikeCount 减少点赞数
DecrementLikeCount(id int64) error
}
// collectionService 典藏服务实现
type collectionService struct {
collectionRepo repository.CollectionRepository
registryRepo repository.AssetRegistryRepository
}
// NewCollectionService 创建典藏服务实例
func NewCollectionService(
collectionRepo repository.CollectionRepository,
registryRepo repository.AssetRegistryRepository,
) CollectionService {
return &collectionService{
collectionRepo: collectionRepo,
registryRepo: registryRepo,
}
}
// Create 创建典藏藏品
func (s *collectionService) Create(asset *models.CollectionAsset) error {
if asset == nil {
return appErrors.ErrInvalidAssetStatus
}
// 创建典藏藏品记录
if err := s.collectionRepo.Create(asset); err != nil {
return err
}
// 同步写入 asset_registry
registry := &models.AssetRegistry{
AssetID: asset.AssetID,
AssetType: models.AssetTypeCollection,
OwnerUID: asset.OwnerUID,
StarID: asset.StarID,
CollectionCategory: &asset.Category,
Status: asset.Status,
LikeCount: asset.LikeCount,
CreatedAt: time.Now().UnixMilli(),
UpdatedAt: time.Now().UnixMilli(),
}
return s.registryRepo.Create(registry)
}
// GetByID 根据ID获取
func (s *collectionService) GetByID(id int64) (*models.CollectionAsset, error) {
return s.collectionRepo.GetByID(id)
}
// GetByAssetID 根据asset_id获取
func (s *collectionService) GetByAssetID(assetID int64) (*models.CollectionAsset, error) {
return s.collectionRepo.GetByAssetID(assetID)
}
// GetByOwner 获取用户的典藏藏品
func (s *collectionService) GetByOwner(ownerUID, starID int64, limit, offset int) ([]*models.CollectionAsset, error) {
return s.collectionRepo.GetByOwner(ownerUID, starID, limit, offset)
}
// GetByOwnerAndCategory 获取用户指定分类的典藏藏品
func (s *collectionService) GetByOwnerAndCategory(ownerUID, starID int64, category string, limit, offset int) ([]*models.CollectionAsset, error) {
return s.collectionRepo.GetByOwnerAndCategory(ownerUID, starID, category, limit, offset)
}
// CountByOwner 统计用户的典藏藏品数量
func (s *collectionService) CountByOwner(ownerUID, starID int64) (int64, error) {
return s.collectionRepo.CountByOwner(ownerUID, starID)
}
// CountByOwnerAndCategory 统计用户指定分类的典藏藏品数量
func (s *collectionService) CountByOwnerAndCategory(ownerUID, starID int64, category string) (int64, error) {
return s.collectionRepo.CountByOwnerAndCategory(ownerUID, starID, category)
}
// IncrementLikeCount 增加点赞数
func (s *collectionService) IncrementLikeCount(id int64) error {
return s.collectionRepo.IncrementLikeCount(id)
}
// DecrementLikeCount 减少点赞数
func (s *collectionService) DecrementLikeCount(id int64) error {
return s.collectionRepo.DecrementLikeCount(id)
}

View File

@ -1,448 +0,0 @@
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
var assetLikeCountMap map[int64]int32 // assetID -> likeCount
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)
assetLikeCountMap = make(map[int64]int32)
for _, asset := range assets {
assetCoverMap[asset.ID] = asset.CoverURL
assetNameMap[asset.ID] = asset.Name
assetLikeCountMap[asset.ID] = asset.LikeCount
}
}
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 && coverURL != "" {
item.CoverUrlSigned = coverURL
}
if cat, ok := categoryMap[reg.AssetID]; ok {
item.Category = cat
}
// 从 assets 表获取点赞数regular 类型)
if assetType == models.AssetTypeRegular {
if likeCount, ok := assetLikeCountMap[reg.AssetID]; ok {
item.LikeCount = likeCount
}
}
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
}