From 588c819c3c59dffd2dfa8ee72556624076720c0f Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 12 Sep 2025 14:39:44 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/api/api.go | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 internal/app/api/api.go diff --git a/internal/app/api/api.go b/internal/app/api/api.go new file mode 100644 index 0000000..9dcda66 --- /dev/null +++ b/internal/app/api/api.go @@ -0,0 +1,117 @@ +package api + +import ( + "context" + "fmt" + "net/http" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/user" + "git.huangwc.com/pig/pig-farm-controller/internal/app/service/token" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/config" // 引入 config 包 + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + "github.com/gin-gonic/gin" +) + +// API 结构体定义了 HTTP 服务器及其依赖 +type API struct { + engine *gin.Engine // Gin 引擎实例,用于处理 HTTP 请求 + logger *logs.Logger // 日志记录器,用于输出日志信息 + userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作 + tokenService token.TokenService // Token 服务接口,用于 JWT token 的生成和解析 + httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务 + config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig + userController *user.Controller // 用户控制器实例 +} + +// NewAPI 创建并返回一个新的 API 实例 +// 负责初始化 Gin 引擎、设置全局中间件,并注入所有必要的依赖。 +func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.UserRepository, tokenService token.TokenService) *API { + // 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式) + // 从配置中获取 Gin 模式 + gin.SetMode(cfg.Mode) + + // 使用 gin.New() 创建一个 Gin 引擎实例,而不是 gin.Default() + // 这样可以手动添加所需的中间件,避免 gin.Default() 默认包含的 Logger 和 Recovery 中间件 + engine := gin.New() + + // 添加 Gin Recovery 中间件,用于捕获 panic 并恢复,防止服务崩溃 + // gin.Logger() 已移除,因为我们使用自定义的 logger + engine.Use(gin.Recovery()) + + // 初始化 API 结构体 + api := &API{ + engine: engine, + logger: logger, + userRepo: userRepo, + tokenService: tokenService, + config: cfg, + // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 + userController: user.NewController(userRepo, logger, tokenService), + } + + api.setupRoutes() // 设置所有路由 + return api +} + +// setupRoutes 设置所有 API 路由 +// 在此方法中,使用已初始化的控制器实例将其路由注册到 Gin 引擎中。 +func (a *API) setupRoutes() { + // 创建 /api/v1 路由组 + v1 := a.engine.Group("/api/v1") + { + // 用户相关路由组 + userGroup := v1.Group("/users") + { + userGroup.POST("", a.userController.CreateUser) // 注册创建用户接口 (POST /api/v1/users) + userGroup.POST("/login", a.userController.Login) // 注册用户登录接口 (POST /api/v1/users/login) + } + } +} + +// Start 启动 HTTP 服务器 +// 接收一个地址字符串 (例如 ":8080"),并在一个新的 goroutine 中启动服务器, +// 以便主线程可以继续执行其他任务(例如监听操作系统信号)。 +func (a *API) Start() { + // 构建监听地址字符串 + addr := fmt.Sprintf(":%d", a.config.Port) + + // 初始化标准库的 http.Server 实例 + a.httpServer = &http.Server{ + Addr: addr, // 服务器监听的地址从配置中获取 + Handler: a.engine, // 将 Gin 引擎作为 HTTP 请求的处理程序 + } + + // 在独立的 goroutine 中启动服务器 + // ListenAndServe 会阻塞,所以放在 goroutine 中可以避免阻塞主线程 + go func() { + // 启动服务器,并检查错误。http.ErrServerClosed 是正常关闭时的错误,无需处理。 + if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + a.logger.Fatalf("HTTP server listen: %s", err) // 如果是其他错误,则记录致命错误并退出 + } + }() + // 记录服务器已启动的信息 + a.logger.Infof("HTTP server listening on %s", addr) +} + +// Stop 优雅地停止 HTTP 服务器 +// 在停止服务器时,会给一个超时时间,确保正在处理的请求能够完成。 +func (a *API) Stop() { + // 记录服务器正在关闭的信息 + a.logger.Info("Shutting down HTTP server...") + + // 创建一个带有 5 秒超时时间的上下文 + // 在此时间内,服务器会尝试完成所有活跃的连接。 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() // 确保上下文在函数退出时被取消,释放资源 + + // 尝试优雅地关闭 HTTP 服务器 + // 如果在超时时间内未能关闭,Shutdown 会返回错误。 + if err := a.httpServer.Shutdown(ctx); err != nil { + // 如果关闭失败,记录致命错误并退出 + a.logger.Fatalf("HTTP server shutdown failed: %s", err) + } + // 记录服务器已停止的信息 + a.logger.Info("HTTP server stopped.") +}