310 lines
11 KiB
Go
310 lines
11 KiB
Go
// @title OCDP Backend API
|
||
// @version 1.0
|
||
// @description OCDP (Open Cloud Development Platform) Backend API
|
||
// @description
|
||
// @description RESTful API for managing Kubernetes clusters, OCI registries, and Helm deployments.
|
||
//
|
||
// @contact.name API Support
|
||
// @contact.email support@ocdp.io
|
||
//
|
||
// @license.name Apache 2.0
|
||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||
//
|
||
// @host localhost:8080
|
||
// @BasePath /api/v1
|
||
//
|
||
// @schemes http https
|
||
//
|
||
// @securityDefinitions.apikey BearerAuth
|
||
// @in header
|
||
// @name Authorization
|
||
// @description Type "Bearer" followed by a space and JWT token.
|
||
package main
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"time"
|
||
|
||
"github.com/gorilla/mux"
|
||
|
||
"github.com/ocdp/cluster-service/internal/adapter/input/http/rest"
|
||
"github.com/ocdp/cluster-service/internal/adapter/output"
|
||
"github.com/ocdp/cluster-service/internal/bootstrap"
|
||
"github.com/ocdp/cluster-service/internal/domain/service"
|
||
"github.com/ocdp/cluster-service/internal/pkg/crypto"
|
||
"github.com/ocdp/cluster-service/internal/pkg/jwt"
|
||
"github.com/ocdp/cluster-service/internal/pkg/password"
|
||
)
|
||
|
||
func main() {
|
||
log.Println("🚀 Starting OCDP Backend (Hexagonal Architecture)")
|
||
|
||
// ===== 1. 读取配置 =====
|
||
config := loadConfig()
|
||
log.Printf("📝 Configuration: mode=%s, port=%s", config.AdapterMode, config.Port)
|
||
|
||
// ===== 2. 创建加密器(用于敏感数据加密存储) =====
|
||
encryptor := crypto.NewAESEncryptor(config.EncryptionKey)
|
||
log.Println("✅ Encryption enabled for sensitive data")
|
||
|
||
// ===== 3. 创建 Output Adapters(通过工厂) =====
|
||
factory := output.NewAdapterFactory(
|
||
output.AdapterMode(config.AdapterMode),
|
||
encryptor,
|
||
config.DatabaseURL,
|
||
)
|
||
|
||
repos, err := factory.CreateAllRepositories()
|
||
if err != nil {
|
||
log.Fatalf("❌ Failed to create adapters: %v", err)
|
||
}
|
||
log.Printf("✅ Output Adapters initialized (mode: %s)", config.AdapterMode)
|
||
|
||
// ===== 4. 创建工具包实例 =====
|
||
passwordHasher := password.NewHasher()
|
||
tokenGenerator := jwt.NewJWTManager(config.JWTSecret)
|
||
log.Println("✅ Utilities initialized")
|
||
|
||
// ===== 5. 创建 Domain Services =====
|
||
authService := service.NewAuthService(
|
||
repos.UserRepo,
|
||
passwordHasher,
|
||
tokenGenerator,
|
||
)
|
||
|
||
clusterService := service.NewClusterService(
|
||
repos.ClusterRepo,
|
||
)
|
||
|
||
registryService := service.NewRegistryService(
|
||
repos.RegistryRepo,
|
||
repos.OCIClient,
|
||
)
|
||
|
||
artifactService := service.NewArtifactService(
|
||
repos.RegistryRepo,
|
||
repos.OCIClient,
|
||
)
|
||
|
||
instanceService := service.NewInstanceService(
|
||
repos.InstanceRepo,
|
||
repos.ClusterRepo,
|
||
repos.RegistryRepo,
|
||
repos.HelmClient,
|
||
repos.OCIClient,
|
||
repos.EntryClient,
|
||
)
|
||
|
||
monitoringService := service.NewMonitoringService(
|
||
repos.ClusterRepo,
|
||
repos.MetricsClient,
|
||
)
|
||
|
||
log.Println("✅ Domain Services initialized")
|
||
|
||
// ===== 6. 加载并执行 Bootstrap 预注入 =====
|
||
bootstrapConfig, err := bootstrap.LoadBootstrapConfig()
|
||
if err != nil {
|
||
log.Printf("⚠️ Warning: Failed to load bootstrap config: %v", err)
|
||
// 使用默认配置
|
||
bootstrapConfig = bootstrap.GetDefaultBootstrapConfig()
|
||
}
|
||
|
||
seeder := bootstrap.NewSeeder(repos, passwordHasher, bootstrapConfig)
|
||
if err := seeder.SeedAll(context.Background()); err != nil {
|
||
log.Printf("⚠️ Warning: Failed to seed data: %v", err)
|
||
}
|
||
|
||
// ===== 7. 创建 Input Adapters (REST Handlers) =====
|
||
authHandler := rest.NewAuthHandler(authService)
|
||
clusterHandler := rest.NewClusterHandler(clusterService)
|
||
registryHandler := rest.NewRegistryHandler(registryService)
|
||
artifactHandler := rest.NewArtifactHandler(artifactService)
|
||
instanceHandler := rest.NewInstanceHandler(instanceService)
|
||
monitoringHandler := rest.NewMonitoringHandler(monitoringService)
|
||
swaggerHandler := rest.NewSwaggerHandler()
|
||
|
||
log.Println("✅ Input Adapters (REST handlers) initialized")
|
||
|
||
// ===== 8. 设置路由 =====
|
||
router := setupRouter(
|
||
authHandler,
|
||
clusterHandler,
|
||
registryHandler,
|
||
artifactHandler,
|
||
instanceHandler,
|
||
monitoringHandler,
|
||
swaggerHandler,
|
||
)
|
||
|
||
// ===== 9. 启动服务器 =====
|
||
addr := fmt.Sprintf(":%s", config.Port)
|
||
log.Printf("🌐 Server starting on %s", addr)
|
||
log.Println("")
|
||
log.Println("📍 Available endpoints:")
|
||
log.Printf(" - REST API: http://localhost:%s/api/v1", config.Port)
|
||
log.Printf(" - Swagger UI: http://localhost:%s/api/docs", config.Port)
|
||
log.Printf(" - OpenAPI Spec: http://localhost:%s/api/docs/openapi.yaml", config.Port)
|
||
log.Printf(" - Health: http://localhost:%s/health", config.Port)
|
||
log.Println("")
|
||
log.Println("✨ Press Ctrl+C to stop")
|
||
log.Println("")
|
||
|
||
if err := http.ListenAndServe(addr, router); err != nil {
|
||
log.Fatalf("❌ Server failed: %v", err)
|
||
}
|
||
}
|
||
|
||
// Config 应用配置
|
||
type Config struct {
|
||
AdapterMode string
|
||
Port string
|
||
JWTSecret string
|
||
EncryptionKey string
|
||
DatabaseURL string
|
||
}
|
||
|
||
// loadConfig 加载配置
|
||
func loadConfig() *Config {
|
||
return &Config{
|
||
AdapterMode: getEnv("ADAPTER_MODE", ""), // 默认为空字符串(真实模式)
|
||
Port: getEnv("PORT", "8080"),
|
||
JWTSecret: getEnv("JWT_SECRET", "your-secret-key-change-in-production"),
|
||
EncryptionKey: getEnv("ENCRYPTION_KEY", "default-encryption-key-change-in-production"),
|
||
DatabaseURL: getEnv("DATABASE_URL", ""),
|
||
}
|
||
}
|
||
|
||
// getEnv 获取环境变量,如果不存在则返回默认值
|
||
func getEnv(key, defaultValue string) string {
|
||
value := os.Getenv(key)
|
||
if value == "" {
|
||
return defaultValue
|
||
}
|
||
return value
|
||
}
|
||
|
||
// setupRouter 设置路由
|
||
func setupRouter(
|
||
authHandler *rest.AuthHandler,
|
||
clusterHandler *rest.ClusterHandler,
|
||
registryHandler *rest.RegistryHandler,
|
||
artifactHandler *rest.ArtifactHandler,
|
||
instanceHandler *rest.InstanceHandler,
|
||
monitoringHandler *rest.MonitoringHandler,
|
||
swaggerHandler *rest.SwaggerHandler,
|
||
) *mux.Router {
|
||
router := mux.NewRouter().StrictSlash(true)
|
||
|
||
// 全局中间件
|
||
router.Use(loggingMiddleware)
|
||
router.Use(corsMiddleware)
|
||
|
||
// 健康检查
|
||
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(http.StatusOK)
|
||
w.Write([]byte(`{"status":"healthy"}`))
|
||
})
|
||
|
||
// ===== Swagger UI =====
|
||
router.HandleFunc("/api/docs", swaggerHandler.ServeSwaggerUI).Methods(http.MethodGet)
|
||
router.HandleFunc("/api/docs/assets/swagger-ui.css", swaggerHandler.ServeSwaggerCSS).Methods(http.MethodGet)
|
||
router.HandleFunc("/api/docs/assets/swagger-ui-bundle.js", swaggerHandler.ServeSwaggerBundle).Methods(http.MethodGet)
|
||
router.HandleFunc("/api/docs/assets/swagger-ui-standalone-preset.js", swaggerHandler.ServeSwaggerStandalonePreset).Methods(http.MethodGet)
|
||
router.HandleFunc("/api/docs/openapi.yaml", swaggerHandler.ServeOpenAPISpec).Methods(http.MethodGet)
|
||
|
||
// API v1
|
||
api := router.PathPrefix("/api/v1").Subrouter()
|
||
|
||
// ===== 认证路由 =====
|
||
api.HandleFunc("/auth/register", authHandler.Register)
|
||
api.HandleFunc("/auth/login", authHandler.Login)
|
||
api.HandleFunc("/auth/refresh", authHandler.RefreshToken)
|
||
|
||
// ===== 集群路由 =====
|
||
api.HandleFunc("/clusters", clusterHandler.CreateCluster).Methods(http.MethodPost)
|
||
api.HandleFunc("/clusters", clusterHandler.GetAllClusters).Methods(http.MethodGet)
|
||
api.HandleFunc("/clusters/{cluster_id}", clusterHandler.GetCluster).Methods(http.MethodGet)
|
||
api.HandleFunc("/clusters/{cluster_id}", clusterHandler.UpdateCluster).Methods(http.MethodPut)
|
||
api.HandleFunc("/clusters/{cluster_id}", clusterHandler.DeleteCluster).Methods(http.MethodDelete)
|
||
api.HandleFunc("/clusters/{cluster_id}/health", clusterHandler.GetClusterHealth).Methods(http.MethodGet)
|
||
|
||
// ===== Registry 路由 =====
|
||
api.HandleFunc("/registries", registryHandler.CreateRegistry).Methods(http.MethodPost)
|
||
api.HandleFunc("/registries", registryHandler.GetAllRegistries).Methods(http.MethodGet)
|
||
api.HandleFunc("/registries/{registry_id}", registryHandler.GetRegistry).Methods(http.MethodGet)
|
||
api.HandleFunc("/registries/{registry_id}", registryHandler.UpdateRegistry).Methods(http.MethodPut)
|
||
api.HandleFunc("/registries/{registry_id}", registryHandler.DeleteRegistry).Methods(http.MethodDelete)
|
||
api.HandleFunc("/registries/{registry_id}/health", registryHandler.GetRegistryHealth).Methods(http.MethodGet)
|
||
|
||
// ===== Artifact 路由 =====
|
||
api.HandleFunc("/registries/{registry_id}/repositories", artifactHandler.ListRepositories).Methods(http.MethodGet)
|
||
api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts", artifactHandler.ListArtifacts).Methods(http.MethodGet)
|
||
api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}", artifactHandler.GetArtifact).Methods(http.MethodGet)
|
||
api.HandleFunc("/registries/{registry_id}/repositories/{repository_name:.+}/artifacts/{reference}/values-schema", artifactHandler.GetArtifactValuesSchema).Methods(http.MethodGet)
|
||
|
||
// ===== Instance 路由 =====
|
||
api.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.CreateInstance).Methods(http.MethodPost)
|
||
api.HandleFunc("/clusters/{cluster_id}/instances", instanceHandler.ListInstances).Methods(http.MethodGet)
|
||
api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.GetInstance).Methods(http.MethodGet)
|
||
api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.UpdateInstance).Methods(http.MethodPut)
|
||
api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}", instanceHandler.DeleteInstance).Methods(http.MethodDelete)
|
||
api.HandleFunc("/clusters/{cluster_id}/instances/{instance_id}/entries", instanceHandler.ListInstanceEntries).Methods(http.MethodGet)
|
||
|
||
// ===== Monitoring 路由 =====
|
||
api.HandleFunc("/monitoring/clusters", monitoringHandler.ListClusterMonitoring).Methods(http.MethodGet)
|
||
api.HandleFunc("/monitoring/clusters/{cluster_id}", monitoringHandler.GetClusterMonitoring).Methods(http.MethodGet)
|
||
api.HandleFunc("/monitoring/clusters/{cluster_id}/nodes", monitoringHandler.GetNodeMetrics).Methods(http.MethodGet)
|
||
api.HandleFunc("/monitoring/summary", monitoringHandler.GetMonitoringSummary).Methods(http.MethodGet)
|
||
|
||
// 处理 MethodNotAllowed 错误(OPTIONS 请求会触发)
|
||
router.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method == http.MethodOptions {
|
||
// CORS 预检已在中间件处理,这里直接返回
|
||
w.WriteHeader(http.StatusNoContent)
|
||
return
|
||
}
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
})
|
||
|
||
return router
|
||
}
|
||
|
||
// loggingMiddleware 日志中间件
|
||
func loggingMiddleware(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
start := time.Now()
|
||
next.ServeHTTP(w, r)
|
||
log.Printf("%s %s %s %v", r.Method, r.RequestURI, r.RemoteAddr, time.Since(start))
|
||
})
|
||
}
|
||
|
||
// corsMiddleware CORS 中间件
|
||
func corsMiddleware(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// 设置 CORS 头
|
||
origin := r.Header.Get("Origin")
|
||
if origin == "" {
|
||
origin = "*"
|
||
}
|
||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
|
||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||
w.Header().Set("Access-Control-Max-Age", "86400")
|
||
|
||
// 处理 OPTIONS 预检请求
|
||
if r.Method == http.MethodOptions {
|
||
w.WriteHeader(http.StatusNoContent)
|
||
return
|
||
}
|
||
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|