update
This commit is contained in:
142
cmd/server/main.go
Normal file
142
cmd/server/main.go
Normal file
@ -0,0 +1,142 @@
|
||||
// Command server 启动 Fashion 对外 API 服务。
|
||||
//
|
||||
// 本文件是唯一的依赖装配点(组合根):配置 → 数据库 → 仓储 → 服务 → 处理器 → 路由。
|
||||
// 依赖自下而上显式传入,不使用包级全局变量,便于替换实现与编写测试。
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"fashionapi/internal/config"
|
||||
"fashionapi/internal/database"
|
||||
"fashionapi/internal/handler"
|
||||
"fashionapi/internal/pkg/hashid"
|
||||
"fashionapi/internal/pkg/jwt"
|
||||
"fashionapi/internal/repository"
|
||||
"fashionapi/internal/router"
|
||||
"fashionapi/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "", "配置文件路径,默认查找 configs/config.yml")
|
||||
flag.Parse()
|
||||
|
||||
// 1. 加载配置(yml 为主,环境变量可覆盖)
|
||||
cfg, err := config.Load(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("✗ 加载配置失败: %v", err)
|
||||
}
|
||||
if from := cfg.LoadedFrom(); from != "" {
|
||||
log.Println("✓ 配置文件:", from)
|
||||
} else {
|
||||
log.Println("! 未找到配置文件,使用默认值与环境变量")
|
||||
}
|
||||
gin.SetMode(cfg.Server.Mode)
|
||||
|
||||
// 2. 连接数据库
|
||||
db, err := database.New(cfg.Database)
|
||||
if err != nil {
|
||||
log.Fatalf("✗ %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := database.Close(db); err != nil {
|
||||
log.Printf("! 关闭数据库连接失败: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Println("✓ MySQL 已连接:", cfg.Database.Addr())
|
||||
|
||||
// 3. 确保上传目录存在(静态文件服务的根目录)
|
||||
uploadDir, _ := filepath.Abs(cfg.Upload.Dir)
|
||||
if err := os.MkdirAll(uploadDir, 0o755); err != nil {
|
||||
log.Fatalf("✗ 创建上传目录失败: %v", err)
|
||||
}
|
||||
log.Printf("✓ 静态资源: %s → %s", cfg.Upload.URLPrefix, uploadDir)
|
||||
|
||||
// 4.5 初始化公开 ID 混淆(HashID)。
|
||||
// 对外接口的 id / brand_id 一律用 hashid 编码串,内部仍用数字主键;
|
||||
// 部署可用 HASHID_SECRET 提供独立盐值(默认走内置盐)。
|
||||
hashid.Init(cfg.Server.HashIDSecret)
|
||||
|
||||
// 5. 装配各层依赖
|
||||
var (
|
||||
articleRepo = repository.NewArticleRepository(db)
|
||||
brandRepo = repository.NewBrandRepository(db)
|
||||
userRepo = repository.NewUserRepository(db)
|
||||
|
||||
jwtManager = jwt.NewManager(cfg.JWT.Secret, cfg.JWT.ExpireHours)
|
||||
|
||||
articleSvc = service.NewArticleService(articleRepo)
|
||||
brandSvc = service.NewBrandService(brandRepo)
|
||||
authSvc = service.NewAuthService(userRepo, jwtManager)
|
||||
)
|
||||
|
||||
engine := router.New(router.Options{
|
||||
Config: cfg,
|
||||
JWT: jwtManager,
|
||||
Article: handler.NewArticleHandler(articleSvc),
|
||||
Brand: handler.NewBrandHandler(brandSvc),
|
||||
Auth: handler.NewAuthHandler(authSvc),
|
||||
Health: handler.NewHealthHandler(),
|
||||
})
|
||||
|
||||
// 6. 启动对外公开 HTTP 服务(8090),并支持优雅关闭
|
||||
srv := &http.Server{
|
||||
Addr: ":" + cfg.Server.Port,
|
||||
Handler: engine,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("🚀 公开服务已启动: http://localhost:%s", cfg.Server.Port)
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("✗ 公开服务启动失败: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 7. 启动 SSG 内部服务(默认 8091,仅供 astro build 构建期调用)。
|
||||
// 该端口由部署层绑定到 127.0.0.1(回环),外部不可达;此处无论如何都启动,
|
||||
// 即便部署层误绑了 0.0.0.0,SSGToken 中间件也能作为最后一道闸。
|
||||
ssgEngine := router.NewSSG(router.Options{
|
||||
Config: cfg,
|
||||
SSG: handler.NewSSGHandler(brandSvc, articleSvc),
|
||||
})
|
||||
srvSSG := &http.Server{
|
||||
Addr: ":" + cfg.Server.SSGPort,
|
||||
Handler: ssgEngine,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("🔒 SSG 内部服务已启动: http://localhost:%s (仅构建期/回环可达)", cfg.Server.SSGPort)
|
||||
if err := srvSSG.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("✗ SSG 内部服务启动失败: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 收到中断信号后,给正在处理的请求留出完成时间再退出,
|
||||
// 避免直接杀进程导致请求被截断、连接池未正常释放。
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("→ 正在关闭服务 ...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(),
|
||||
time.Duration(cfg.Server.ShutdownTimeout)*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Printf("! 公开服务优雅关闭超时: %v", err)
|
||||
}
|
||||
if err := srvSSG.Shutdown(ctx); err != nil {
|
||||
log.Printf("! SSG 内部服务优雅关闭超时: %v", err)
|
||||
}
|
||||
log.Println("✓ 服务已停止")
|
||||
}
|
||||
Reference in New Issue
Block a user