Skip to content

Commit 64bd14d

Browse files
author
chendelin1982
committed
pkg/db,pkg/env
1 parent d43c99b commit 64bd14d

30 files changed

+3388
-285
lines changed

Makefile

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ swagger:
4343
@echo "Generating Swagger API documentation..."
4444
@cd core && swag init -g cmd/server/main.go -o docs
4545
@echo "✅ Swagger docs generated in core/docs/"
46-
@echo "Access at: http://localhost:8080/api/docs/"
46+
@echo "Access at: http://localhost:$${HTTP_PORT:-8080}/api/docs/"
4747

4848
# 测试
4949
test-all: test-unit test-integration
@@ -96,8 +96,8 @@ clean:
9696
# 开发环境
9797
dev: docker-up
9898
@echo "Development environment started"
99-
@echo "App: http://localhost:8080"
100-
@echo "Config API: http://localhost:8080/config"
99+
@echo "App: http://localhost:$${HTTP_PORT:-8080}"
100+
@echo "Config API: http://localhost:$${HTTP_PORT:-8080}/config"
101101

102102
# 快速测试
103103
test-config: test-unit
@@ -150,8 +150,13 @@ dev-down:
150150
run-local:
151151
@echo "🏃 Running app locally..."
152152
@echo "📌 Make sure dependencies are running: make dev-up"
153+
@echo "📝 Using development database configuration"
153154
@echo ""
154-
cd core && go run ./cmd/server/main.go
155+
cd core && \
156+
DATABASE_USER=apprun \
157+
DATABASE_PASSWORD=dev_password_123 \
158+
DATABASE_DB_NAME=apprun_dev \
159+
go run ./cmd/server/main.go
155160

156161
# Build Docker image locally
157162
build-local:
@@ -168,7 +173,7 @@ test-local: build-local
168173
@echo "⏳ Waiting for services to be ready..."
169174
@sleep 15
170175
@echo "🔍 Checking health..."
171-
@docker exec apprun-app-local wget -q -O- http://localhost:8080/health || (echo "❌ Health check failed" && docker compose -f docker-compose.local.yml down && exit 1)
176+
@docker exec apprun-app-local wget -q -O- http://localhost:$${HTTP_PORT:-8080}/health || (echo "❌ Health check failed" && docker compose -f docker-compose.local.yml down && exit 1)
172177
@echo "✅ Integration tests passed!"
173178
@docker compose -f docker-compose.local.yml down
174179

@@ -179,8 +184,8 @@ prod-up-local:
179184
@echo "✅ Local production environment started!"
180185
@echo ""
181186
@echo "🔗 Access:"
182-
@echo " HTTP: http://localhost:8080"
183-
@echo " HTTPS: https://localhost:8443"
187+
@echo " HTTP: http://localhost:$${HTTP_PORT:-8080}"
188+
@echo " HTTPS: https://localhost:$${HTTPS_PORT:-8443}"
184189
@echo ""
185190
@echo "📊 View logs:"
186191
@echo " docker compose -f docker-compose.local.yml logs -f"

core/cmd/server/main.go

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package main
33
import (
44
"context"
55
"log"
6-
"net/http"
7-
"os"
6+
"time"
87

98
_ "apprun/docs" // Swagger docs (自动生成)
10-
internalConfig "apprun/internal/config"
119
"apprun/modules/config"
10+
"apprun/pkg/database"
11+
"apprun/pkg/env"
12+
"apprun/pkg/logger"
13+
"apprun/pkg/server"
1214
"apprun/routes"
1315

1416
_ "github.com/lib/pq"
@@ -25,31 +27,57 @@ import (
2527
// @license.name Apache 2.0
2628
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
2729

30+
// @host localhost:8080
2831
// @BasePath /api
2932

3033
// @schemes http https
3134
func main() {
32-
ctx := context.Background()
33-
34-
// 创建配置引导器
35-
bootstrap := config.NewBootstrap(getEnv("CONFIG_DIR", "./config"))
35+
// Recover from panics during startup (e.g., missing required environment variables)
36+
defer func() {
37+
if r := recover(); r != nil {
38+
log.Fatalf("❌ Startup failed: %v", r)
39+
}
40+
}()
41+
42+
// Phase 0: Load infrastructure config from file to environment variables
43+
// This allows default.yaml to provide defaults while respecting existing env vars
44+
// Priority: runtime env > config file > code defaults
45+
configDir := env.Get("CONFIG_DIR", "./config")
46+
if err := env.LoadConfigToEnv(configDir); err != nil {
47+
log.Printf("⚠️ Warning: Failed to load config file: %v", err)
48+
log.Println("⚠️ Using environment variables and code defaults only")
49+
}
3650

37-
// 1. 加载初始配置
38-
cfg, err := bootstrap.LoadInitialConfig(ctx)
39-
if err != nil {
40-
log.Fatalf("❌ Failed to load initial config: %v", err)
51+
// Create context with timeout for startup phase
52+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
53+
defer cancel()
54+
55+
// Phase 1: Initialize Configuration Module Registry
56+
// Register business module configurations for centralized management
57+
// Note: Infrastructure configs (server, database) are NOT registered here
58+
// They are managed via environment variables loaded in Phase 0
59+
registry := config.NewRegistry()
60+
if err := registry.Register("logger", &logger.Config{}); err != nil {
61+
log.Fatalf("❌ Failed to register logger config: %v", err)
4162
}
42-
log.Printf("✅ Config loaded: %s v%s", cfg.App.Name, cfg.App.Version)
63+
log.Println("✅ Logger module registered with config center")
4364

44-
// 2. 初始化数据库
45-
dbClient, err := bootstrap.InitDatabase(ctx, cfg)
65+
// Create configuration bootstrapper with registry
66+
bootstrap := config.NewBootstrapWithRegistry(env.Get("CONFIG_DIR", "./config"), registry)
67+
68+
// Phase 2: Connect to Database (Layer 1 infrastructure)
69+
// Database connection is required for config service and business logic
70+
// Note: DB_PASSWORD environment variable is required (set via Phase 0 or export)
71+
dbCfg := database.DefaultConfig()
72+
dbClient, err := database.Connect(ctx, dbCfg)
4673
if err != nil {
47-
log.Fatalf("❌ Failed to initialize database: %v", err)
74+
log.Fatalf("❌ Failed to connect to database: %v", err)
4875
}
4976
defer dbClient.Close()
5077
log.Println("✅ Database connected")
5178

52-
// 3. 创建配置服务
79+
// Phase 3: Initialize Config Service (Layer 2 - Configuration Center)
80+
// Config service manages runtime configurations stored in database
5381
configService, err := bootstrap.CreateService(ctx, dbClient)
5482
if err != nil {
5583
log.Printf("⚠️ Warning: Failed to create config service: %v", err)
@@ -58,56 +86,55 @@ func main() {
5886
log.Println("✅ Config service initialized with DB support")
5987
}
6088

61-
// 4. 设置路由
62-
router := routes.SetupRoutes(configService)
63-
64-
// 5. 启动服务器
65-
startServer(router, cfg)
66-
}
67-
68-
// startServer 启动 HTTP/HTTPS 服务器
69-
func startServer(router http.Handler, cfg *internalConfig.Config) {
70-
// 获取 TLS 配置
71-
sslCertFile := os.Getenv("SSL_CERT_FILE")
72-
sslKeyFile := os.Getenv("SSL_KEY_FILE")
73-
httpPort := getEnv("SERVER_PORT", "8080")
74-
httpsPort := getEnv("HTTPS_PORT", "8443")
75-
76-
// 检查是否启用 TLS
77-
if sslCertFile != "" && sslKeyFile != "" {
78-
// 启动 HTTPS 服务器
79-
log.Printf("🔒 Starting HTTPS server on :%s", httpsPort)
80-
log.Printf("📄 Using certificate: %s", sslCertFile)
81-
82-
// 同时启动 HTTP 服务器(用于健康检查和可能的重定向)
83-
go func() {
84-
httpAddr := ":" + httpPort
85-
log.Printf("🌐 Starting HTTP server on %s (for health checks)", httpAddr)
86-
if err := http.ListenAndServe(httpAddr, router); err != nil {
87-
log.Fatalf("HTTP server failed: %v", err)
88-
}
89-
}()
90-
91-
// 启动 HTTPS 服务器
92-
httpsAddr := ":" + httpsPort
93-
if err := http.ListenAndServeTLS(httpsAddr, sslCertFile, sslKeyFile, router); err != nil {
94-
log.Fatalf("HTTPS server failed: %v", err)
95-
}
89+
// Phase 4: Initialize Business Logger (Layer 2 - Runtime Logger)
90+
// Business logger is used for application runtime logging (request handling, business logic)
91+
// Startup logs continue using standard log package (this is still bootstrap phase)
92+
loggerCfg := logger.Config{
93+
Level: logger.LevelInfo, // Default level, can be overridden by config service
94+
Output: logger.OutputConfig{
95+
Targets: []string{"stdout"},
96+
},
97+
}
98+
// TODO: Load logger config from config service in future iterations
99+
// For now, use default config for business logger initialization
100+
businessLogger, err := logger.NewZapLogger(loggerCfg)
101+
if err != nil {
102+
log.Printf("⚠️ Warning: Failed to initialize business logger: %v", err)
103+
log.Println("⚠️ Using NopLogger (no-op) for runtime logging")
104+
// Fallback to NopLogger if initialization fails
96105
} else {
97-
// 仅启动 HTTP 服务器
98-
addr := ":" + httpPort
99-
log.Printf("🌐 Starting HTTP server on %s", addr)
100-
log.Printf("💡 Tip: Set SSL_CERT_FILE and SSL_KEY_FILE to enable HTTPS")
101-
if err := http.ListenAndServe(addr, router); err != nil {
102-
log.Fatalf("HTTP server failed: %v", err)
103-
}
106+
logger.SetLogger(businessLogger)
107+
defer businessLogger.Close()
108+
log.Println("✅ Business logger initialized (runtime logging ready)")
104109
}
105-
}
106110

107-
// getEnv 获取环境变量,如果不存在则返回默认值
108-
func getEnv(key, defaultValue string) string {
109-
if value := os.Getenv(key); value != "" {
110-
return value
111+
// Phase 5: Setup HTTP Routes
112+
// Register all HTTP handlers and middleware
113+
router := routes.SetupRoutes(configService)
114+
log.Println("✅ HTTP routes configured")
115+
116+
// Phase 6: Configure HTTP/HTTPS Server
117+
// Server configuration is automatically loaded from environment variables by DefaultConfig()
118+
// Environment variables are set in Phase 0 by LoadConfigToEnv() from default.yaml
119+
// Naming convention: SERVER_HTTP_PORT, SERVER_HTTPS_PORT, SERVER_SSL_CERT_FILE, etc.
120+
serverCfg := server.DefaultConfig()
121+
122+
// Print startup summary (still using standard log - bootstrap phase)
123+
log.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
124+
log.Println("🚀 AppRun Server Starting...")
125+
log.Printf(" Database: %s@%s:%d/%s", dbCfg.User, dbCfg.Host, dbCfg.Port, dbCfg.DBName)
126+
log.Printf(" HTTP Port: %s", serverCfg.HTTPPort)
127+
if serverCfg.SSLCertFile != "" {
128+
log.Printf(" HTTPS Port: %s (TLS Enabled)", serverCfg.HTTPSPort)
129+
}
130+
log.Printf(" Config Dir: %s", env.Get("CONFIG_DIR", "./config"))
131+
log.Printf(" Logger Level: %s", loggerCfg.Level)
132+
log.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
133+
log.Println("📝 Note: Using standard log for startup, business logger for runtime")
134+
135+
// Phase 7: Start HTTP/HTTPS Server (enters runtime phase)
136+
// From this point, handlers will use logger.L() for business logging
137+
if err := server.Start(router, serverCfg); err != nil {
138+
log.Fatalf("❌ Server failed: %v", err)
111139
}
112-
return defaultValue
113140
}

core/config/default.yaml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
app:
22
name: apprun
33
version: "1.0.0"
4+
timezone: "Asia/Shanghai" # or "America/New_York", "Europe/London"
45

56
database:
67
driver: postgres
78
host: localhost
89
port: 5432
910
user: apprun
1011
password: "dev_password_123"
11-
dbname: apprun_dev
12+
db_name: apprun_dev
1213

1314
poc:
1415
enabled: true
1516
database: "postgres://user:pass@localhost:5432/apprun_poc"
16-
apikey: "poc-api-key-123456"
17+
apikey: "poc-api-key-123456"
18+
19+
logger:
20+
level: info
21+
output:
22+
targets: ["stdout"]
23+
24+
# Server configuration (infrastructure, not managed by config center)
25+
# Override via environment variables following naming convention:
26+
# Pattern: {GROUP}_UPPERCASE_{KEY}_UPPERCASE
27+
# Examples: SERVER_HTTP_PORT, SERVER_HTTPS_PORT, SERVER_SSL_CERT_FILE
28+
server:
29+
http_port: "8080"
30+
https_port: "8443"
31+
ssl_cert_file: "" # Path to SSL certificate (empty = HTTP only)
32+
ssl_key_file: "" # Path to SSL private key
33+
shutdown_timeout: "30s"
34+
enable_http_with_https: true # Enable HTTP when HTTPS is active (for health checks)

core/internal/config/types.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ package config
55
// Only nested structs require explicit yaml tags to define root keys
66
type Config struct {
77
App struct {
8-
Name string `validate:"required,min=1" default:"apprun" db:"true"`
9-
Version string `validate:"required" default:"1.0.0" db:"false"`
8+
Name string `validate:"required,min=1" default:"apprun" db:"true"`
9+
Version string `validate:"required" default:"1.0.0" db:"false"`
10+
Timezone string `validate:"required,timezone" default:"Asia/Shanghai" db:"true"`
1011
} `yaml:"app" validate:"required"`
1112

1213
Database struct {
1314
Driver string `validate:"required,oneof=postgres mysql" default:"postgres" db:"false"`
1415
Host string `validate:"required" default:"localhost" db:"false"`
1516
Port int `validate:"required,min=1,max=65535" default:"5432" db:"false"`
1617
User string `validate:"required" default:"postgres" db:"false"`
17-
Password string `validate:"required,min=8" db:"false"`
18-
DBName string `validate:"required" default:"apprun" db:"false"`
18+
Password string `yaml:"password" validate:"required,min=8" db:"false"`
19+
DBName string `yaml:"dbname" validate:"required" default:"apprun" db:"false"`
1920
} `yaml:"database" validate:"required"`
2021

2122
POC struct {
22-
Enabled bool `default:"true" db:"true"`
23-
Database string `validate:"required,url" default:"postgres://user:pass@localhost:5432/apprun_poc" db:"true"`
24-
APIKey string `validate:"required,min=10" db:"true"`
23+
Enabled bool `yaml:"enabled" default:"true" db:"true"`
24+
Database string `yaml:"database" validate:"required,url" default:"postgres://user:pass@localhost:5432/apprun_poc" db:"true"`
25+
APIKey string `yaml:"api_key" validate:"required,min=10" db:"true"`
2526
} `yaml:"poc" validate:"required"`
2627
}

0 commit comments

Comments
 (0)