feat: 部署初版测试
This commit is contained in:
@@ -1,53 +1,214 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
chiMiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/zs/InsightReply/internal/handler"
|
||||
appMiddleware "github.com/zs/InsightReply/internal/middleware"
|
||||
"github.com/zs/InsightReply/internal/repository"
|
||||
"github.com/zs/InsightReply/internal/service"
|
||||
"github.com/zs/InsightReply/internal/worker"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Database connection (Using the provided string)
|
||||
dsn := "postgres://root:QG%237X*HHt3CqbZ@100.64.0.5:5432/InsightReply?sslmode=disable"
|
||||
// 1. Load environment variables
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Println("No .env file found, relying on system environment variables")
|
||||
}
|
||||
|
||||
// 1.5 Setup application file logging
|
||||
if logPath := os.Getenv("LOG_FILE_PATH"); logPath != "" {
|
||||
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err == nil {
|
||||
multiWriter := io.MultiWriter(os.Stdout, logFile)
|
||||
log.SetOutput(multiWriter)
|
||||
log.Printf("Application logs are now being mirrored to: %s", logPath)
|
||||
} else {
|
||||
log.Printf("Failed to open log file %s: %v", logPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Database connection setup
|
||||
dsn := os.Getenv("DATABASE_URL")
|
||||
if dsn == "" {
|
||||
log.Fatal("DATABASE_URL environment variable is required")
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("failed to connect database:", err)
|
||||
}
|
||||
|
||||
fmt.Println("Database connection established")
|
||||
|
||||
// Initialize Layers
|
||||
// 2.1 Run Database Migrations
|
||||
log.Println("Running database migrations...")
|
||||
m, err := migrate.New("file://migrations", dsn)
|
||||
if err != nil {
|
||||
log.Printf("Failed to initialize migrate, skipping: %v", err)
|
||||
} else {
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
log.Printf("Failed to run migrate (maybe tables already exist), continuing: %v", err)
|
||||
} else {
|
||||
log.Println("Database migrations applied successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Initialize Layers
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
userSvc := service.NewUserService(userRepo)
|
||||
userHandler := handler.NewUserHandler(userSvc)
|
||||
|
||||
// AI Service (Using Env Var for API Key)
|
||||
apiKey := os.Getenv("OPENAI_API_KEY")
|
||||
aiSvc := service.NewAIService(apiKey)
|
||||
aiHandler := handler.NewAIHandler(aiSvc)
|
||||
profileRepo := repository.NewProductProfileRepository(db)
|
||||
profileSvc := service.NewProductProfileService(profileRepo)
|
||||
profileHandler := handler.NewProductProfileHandler(profileSvc)
|
||||
|
||||
// Router setup
|
||||
strategyRepo := repository.NewCustomStrategyRepository(db)
|
||||
strategySvc := service.NewCustomStrategyService(strategyRepo)
|
||||
strategyHandler := handler.NewCustomStrategyHandler(strategySvc)
|
||||
|
||||
monitorRepo := repository.NewCompetitorMonitorRepository(db)
|
||||
monitorSvc := service.NewCompetitorMonitorService(monitorRepo)
|
||||
monitorHandler := handler.NewCompetitorMonitorHandler(monitorSvc)
|
||||
|
||||
// AI Service (Multi-LLM Routing Support)
|
||||
aiSvc := service.NewAIService()
|
||||
aiHandler := handler.NewAIHandler(aiSvc, profileSvc, strategySvc)
|
||||
|
||||
// Start Background Workers & Tweet Handling
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel() // Ensure cleanup on exit
|
||||
tweetRepo := repository.NewTweetRepository(db)
|
||||
replyRepo := repository.NewReplyRepository(db)
|
||||
|
||||
tweetHandler := handler.NewTweetHandler(tweetRepo) // Mount API handler
|
||||
replyHandler := handler.NewReplyHandler(replyRepo)
|
||||
|
||||
monitorWorker := worker.NewMonitorWorker(monitorRepo, tweetRepo)
|
||||
go monitorWorker.Start(ctx, 15*time.Minute)
|
||||
|
||||
performanceWorker := worker.NewPerformanceWorker(replyRepo, aiSvc)
|
||||
go performanceWorker.Start(ctx, 30*time.Minute) // Runs every 30 minutes to check 24h+ old replies
|
||||
|
||||
// 4. Router setup
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(chiMiddleware.Logger)
|
||||
r.Use(chiMiddleware.Recoverer)
|
||||
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Post("/users/register", userHandler.Register)
|
||||
r.Get("/ai/test", aiHandler.Test)
|
||||
r.Post("/ai/generate", aiHandler.Generate)
|
||||
// CORS Configuration
|
||||
corsOrigins := os.Getenv("CORS_ORIGINS")
|
||||
if corsOrigins == "" {
|
||||
corsOrigins = "*" // default fallback
|
||||
}
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{corsOrigins},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
// 5. Routes
|
||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"code":200,"message":"ok","data":{"status":"healthy","version":"1.0.0"}}`))
|
||||
})
|
||||
|
||||
fmt.Println("Server starting on :8080")
|
||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||
log.Fatal(err)
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// Public routes
|
||||
r.Post("/users/register", userHandler.Register)
|
||||
|
||||
// Protected routes
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(appMiddleware.JWTAuth)
|
||||
r.Use(appMiddleware.RateLimit(db))
|
||||
|
||||
// User and Profile APIs
|
||||
r.Get("/users/me", userHandler.GetProfile)
|
||||
r.Put("/users/me/preferences", userHandler.UpdatePreferences)
|
||||
r.Get("/users/me/product_profiles", profileHandler.GetProfile)
|
||||
r.Put("/users/me/product_profiles", profileHandler.SaveProfile)
|
||||
|
||||
// Strategy APIs
|
||||
r.Get("/users/me/strategies", strategyHandler.ListStrategies)
|
||||
r.Post("/users/me/strategies", strategyHandler.CreateStrategy)
|
||||
r.Delete("/users/me/strategies/{id}", strategyHandler.DeleteStrategy)
|
||||
|
||||
// Monitor APIs
|
||||
r.Get("/monitors/competitors", monitorHandler.ListMonitors)
|
||||
r.Post("/monitors/competitors", monitorHandler.CreateMonitor)
|
||||
r.Delete("/monitors/competitors/{id}", monitorHandler.DeleteMonitor)
|
||||
|
||||
// Hot Opportunity Tweers API
|
||||
r.Get("/tweets/hot", tweetHandler.GetHotTweets)
|
||||
r.Get("/tweets/search", tweetHandler.GetSearchTweets)
|
||||
|
||||
// AI APIs
|
||||
r.Get("/ai/test", aiHandler.Test)
|
||||
r.Post("/ai/generate", aiHandler.Generate)
|
||||
r.Post("/replies/record", replyHandler.RecordReply)
|
||||
r.Get("/replies", replyHandler.GetReplies)
|
||||
})
|
||||
})
|
||||
|
||||
// 6. Graceful Shutdown Setup
|
||||
port := os.Getenv("SERVER_PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
srv := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
serverErrors := make(chan error, 1)
|
||||
|
||||
// Start the server
|
||||
go func() {
|
||||
fmt.Printf("Server starting on :%s\n", port)
|
||||
serverErrors <- srv.ListenAndServe()
|
||||
}()
|
||||
|
||||
// Channel to listen for an interrupt or terminate signal from the OS.
|
||||
shutdown := make(chan os.Signal, 1)
|
||||
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Blocking main and waiting for shutdown.
|
||||
select {
|
||||
case err := <-serverErrors:
|
||||
log.Fatalf("Error starting server: %v", err)
|
||||
case sig := <-shutdown:
|
||||
log.Printf("Start shutdown... signal: %v", sig)
|
||||
|
||||
// Create context with timeout for shutdown
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Printf("Graceful shutdown did not complete in time: %v", err)
|
||||
if err := srv.Close(); err != nil {
|
||||
log.Fatalf("Could not stop server gracefully: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("Server stopped")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user