topfans/backend/services/galleryService/main.go
zerosaturation c5bf9df955 feat: 经济系统重构 - 任务服务与画廊服务解耦
- galleryService 独立启动,配置 task-service-url
- 新增 taskService 到 galleryService 的 RPC 调用
- 更新 proto 文件并重新生成代码
- 新增展览唯一约束迁移脚本
- 修复多个 service 的配置和依赖关系

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00

230 lines
7.7 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 main
import (
"flag"
"fmt"
"os"
"os/signal"
"strconv"
"syscall"
dubboclient "dubbo.apache.org/dubbo-go/v3/client"
_ "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"
pbAsset "github.com/topfans/backend/pkg/proto/asset"
pbGallery "github.com/topfans/backend/pkg/proto/gallery"
pbTask "github.com/topfans/backend/pkg/proto/task"
pbUser "github.com/topfans/backend/pkg/proto/user"
rpcclient "github.com/topfans/backend/services/galleryService/client"
"github.com/topfans/backend/services/galleryService/provider"
"github.com/topfans/backend/services/galleryService/repository"
"github.com/topfans/backend/services/galleryService/service"
)
var (
port = flag.Int("port", getEnvInt("PORT", 20001), "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")
assetServiceURL = flag.String("asset-service-url", getEnv("ASSET_SERVICE_URL", "tri://localhost:20003"), "Asset service URL")
userServiceURL = flag.String("user-service-url", getEnv("USER_SERVICE_URL", "tri://localhost:20000"), "User service URL")
taskServiceURL = flag.String("task-service-url", getEnv("TASK_SERVICE_URL", "tri://localhost:20002"), "Task service URL")
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: "gallery-service",
Environment: env,
LogLevel: os.Getenv("LOG_LEVEL"),
}); err != nil {
panic(fmt.Sprintf("Failed to initialize logger: %v", err))
}
defer logger.Sync()
logger.Logger.Info("Starting Gallery Service...")
// 初始化数据库
dbConfig := database.Config{
Host: *dbHost,
Port: *dbPort,
User: *dbUser,
Password: *dbPassword,
DBName: *dbName,
SSLMode: "disable",
TimeZone: "Asia/Shanghai",
}
if err := database.Init(dbConfig); err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to initialize database: %v", err))
}
logger.Logger.Info("Database initialized successfully")
// 启动健康检查 HTTP 服务器
healthPort := *port + 1000 // e.g., 20001 -> 21001
healthHandler = health.NewHandler("gallery-service", healthPort)
healthHandler.Start()
// 创建 Repository 层实例
db := database.GetDB()
// 安全迁移:只在表不存在时才创建表,不自动重建
// 这样可以避免 Gorm AutoMigrate 在检测到索引不匹配时重建表导致数据丢失
if !db.Migrator().HasTable(&models.Exhibition{}) {
logger.Logger.Info("Exhibitions table does not exist, creating...")
if err := db.AutoMigrate(&models.Exhibition{}); err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to migrate Exhibition table: %v", err))
}
logger.Logger.Info("Exhibition table created successfully")
} else {
logger.Logger.Info("Exhibitions table already exists, skipping AutoMigrate to preserve data")
}
// 同样处理 booth_slots 表
if !db.Migrator().HasTable(&models.BoothSlot{}) {
logger.Logger.Info("BoothSlots table does not exist, creating...")
if err := db.AutoMigrate(&models.BoothSlot{}); err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to migrate BoothSlot table: %v", err))
}
logger.Logger.Info("BoothSlot table created successfully")
} else {
logger.Logger.Info("BoothSlots table already exists, skipping AutoMigrate to preserve data")
}
galleryRepo := repository.NewGalleryRepository(db)
logger.Logger.Info("Repository layer initialized")
// 创建 Dubbo 客户端
assetCli, err := dubboclient.NewClient(
dubboclient.WithClientURL(*assetServiceURL),
)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create Asset Service Dubbo client: %v", err))
}
userCli, err := dubboclient.NewClient(
dubboclient.WithClientURL(*userServiceURL),
)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create User Service Dubbo client: %v", err))
}
// 获取 Asset Service RPC 客户端
assetServiceClient, err := pbAsset.NewAssetService(assetCli)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create Asset Service RPC client: %v", err))
}
assetRPCClient := rpcclient.NewAssetRPCClient(assetServiceClient)
logger.Logger.Info("Asset Service RPC client initialized")
// 获取 User Service RPC 客户端
userServiceClient, err := pbUser.NewUserSocialService(userCli)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create User Service RPC client: %v", err))
}
userRPCClient := rpcclient.NewUserRPCClient(userServiceClient)
logger.Logger.Info("User Service RPC client initialized")
// 创建 Task Service Dubbo 客户端
taskCli, err := dubboclient.NewClient(
dubboclient.WithClientURL(*taskServiceURL),
)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create Task Service Dubbo client: %v", err))
}
// 获取 Task Service RPC 客户端
taskServiceClient, err := pbTask.NewTaskInternalService(taskCli)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create Task Service RPC client: %v", err))
}
taskRPCClient := rpcclient.NewTaskRPCClient(taskServiceClient)
logger.Logger.Info("Task Service RPC client initialized")
// 创建 Service 层实例
galleryService := service.NewGalleryService(galleryRepo, assetRPCClient, userRPCClient)
slotService := service.NewSlotService(galleryRepo, userRPCClient)
exhibitionService := service.NewExhibitionService(galleryRepo, assetRPCClient)
logger.Logger.Info("Service layer initialized")
// 创建并启动清理 Worker注入 assetRPCClient, userClient 和 taskClient
cleanupWorker := service.NewCleanupWorker(galleryRepo, assetRPCClient, userRPCClient, taskRPCClient)
go cleanupWorker.Start()
logger.Logger.Info("Cleanup worker started")
// 创建 Provider 层实例
galleryProvider := provider.NewGalleryProvider(galleryService, slotService, exhibitionService)
logger.Logger.Info("Provider layer initialized")
// 创建 Dubbo 服务器
srv, err := server.NewServer(
server.WithServerProtocol(
protocol.WithPort(*port),
protocol.WithTriple(),
),
)
if err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to create Dubbo server: %v", err))
}
// 使用 Triple 协议生成的 RegisterHandler 函数注册服务
if err := pbGallery.RegisterGalleryServiceHandler(srv, galleryProvider); err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to register Gallery Service: %v", err))
}
// 启动服务
if err := srv.Serve(); err != nil {
logger.Logger.Fatal(fmt.Sprintf("Failed to start Gallery Service: %v", err))
}
logger.Logger.Info(fmt.Sprintf("Gallery Service started successfully on port %d", *port))
// 等待退出信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Logger.Info("Shutting down Gallery Service...")
// 停止健康检查服务器
if healthHandler != nil {
healthHandler.Stop()
}
// 停止清理 Worker
cleanupWorker.Stop()
logger.Logger.Info("Cleanup worker stopped")
}