package api // @title 猪场管理系统 API // @version 1.0 // @description 这是一个用于管理猪场设备的后端服务。 // @contact.name Divano // @contact.url http://www.example.com // @contact.email divano@example.com // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html import ( "context" "fmt" "net/http" "net/http/pprof" "time" _ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller/user" "git.huangwc.com/pig/pig-farm-controller/internal/app/middleware" "git.huangwc.com/pig/pig-farm-controller/internal/app/service/audit" "git.huangwc.com/pig/pig-farm-controller/internal/app/service/task" "git.huangwc.com/pig/pig-farm-controller/internal/app/service/token" "git.huangwc.com/pig/pig-farm-controller/internal/app/service/transport" "git.huangwc.com/pig/pig-farm-controller/internal/infra/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" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) // API 结构体定义了 HTTP 服务器及其依赖 type API struct { engine *gin.Engine // Gin 引擎实例,用于处理 HTTP 请求 logger *logs.Logger // 日志记录器,用于输出日志信息 userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作 tokenService token.TokenService // Token 服务接口,用于 JWT token 的生成和解析 auditService audit.Service // 审计服务,用于记录用户操作 httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务 config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig userController *user.Controller // 用户控制器实例 deviceController *device.Controller // 设备控制器实例 planController *plan.Controller // 计划控制器实例 listenHandler transport.ListenHandler // 设备上行事件监听器 analysisTaskManager *task.AnalysisPlanTaskManager // 计划触发器管理器实例 } // NewAPI 创建并返回一个新的 API 实例 // 负责初始化 Gin 引擎、设置全局中间件,并注入所有必要的依赖。 func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.UserRepository, deviceRepository repository.DeviceRepository, areaControllerRepository repository.AreaControllerRepository, planRepository repository.PlanRepository, userActionLogRepository repository.UserActionLogRepository, tokenService token.TokenService, auditService audit.Service, // 注入审计服务 listenHandler transport.ListenHandler, analysisTaskManager *task.AnalysisPlanTaskManager) *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, auditService: auditService, config: cfg, listenHandler: listenHandler, // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService), // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 deviceController: device.NewController(deviceRepository, areaControllerRepository, logger), // 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 planController: plan.NewController(logger, planRepository, analysisTaskManager), } api.setupRoutes() // 设置所有路由 return api } // setupRoutes 设置所有 API 路由 // 在此方法中,使用已初始化的控制器实例将其路由注册到 Gin 引擎中。 func (a *API) setupRoutes() { // --- Public Routes --- // 这些路由不需要身份验证 // 用户注册和登录 a.engine.POST("/api/v1/users", a.userController.CreateUser) a.engine.POST("/api/v1/users/login", a.userController.Login) a.logger.Info("公开接口注册成功:用户注册、登录") // 注册 pprof 路由 pprofGroup := a.engine.Group("/debug/pprof") { pprofGroup.GET("/", gin.WrapF(pprof.Index)) pprofGroup.GET("/cmdline", gin.WrapF(pprof.Cmdline)) pprofGroup.GET("/profile", gin.WrapF(pprof.Profile)) pprofGroup.POST("/symbol", gin.WrapF(pprof.Symbol)) pprofGroup.GET("/symbol", gin.WrapF(pprof.Symbol)) pprofGroup.GET("/trace", gin.WrapF(pprof.Trace)) pprofGroup.GET("/allocs", gin.WrapH(pprof.Handler("allocs"))) pprofGroup.GET("/block", gin.WrapH(pprof.Handler("block"))) pprofGroup.GET("/goroutine", gin.WrapH(pprof.Handler("goroutine"))) pprofGroup.GET("/heap", gin.WrapH(pprof.Handler("heap"))) pprofGroup.GET("/mutex", gin.WrapH(pprof.Handler("mutex"))) pprofGroup.GET("/threadcreate", gin.WrapH(pprof.Handler("threadcreate"))) } a.logger.Info("pprof 接口注册成功") // 上行事件监听路由 a.engine.POST("/upstream", func(c *gin.Context) { h := a.listenHandler.Handler() h.ServeHTTP(c.Writer, c.Request) }) a.logger.Info("上行事件监听接口注册成功") // 添加 Swagger UI 路由, Swagger UI可在 /swagger/index.html 上找到 a.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) a.logger.Info("Swagger UI 接口注册成功") // --- Authenticated Routes --- // 所有在此注册的路由都需要通过 JWT 身份验证 authGroup := a.engine.Group("/api/v1") authGroup.Use(middleware.AuthMiddleware(a.tokenService, a.userRepo)) // 1. 身份认证 authGroup.Use(middleware.AuditLogMiddleware(a.auditService)) // 2. 审计日志 { // 用户相关路由组 userGroup := authGroup.Group("/users") { userGroup.GET("/:id/history", a.userController.ListUserHistory) } a.logger.Info("用户相关接口注册成功 (需要认证和审计)") // 设备相关路由组 deviceGroup := authGroup.Group("/devices") { deviceGroup.POST("", a.deviceController.CreateDevice) deviceGroup.GET("", a.deviceController.ListDevices) deviceGroup.GET("/:id", a.deviceController.GetDevice) deviceGroup.PUT("/:id", a.deviceController.UpdateDevice) deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice) } a.logger.Info("设备相关接口注册成功 (需要认证和审计)") // 区域主控相关路由组 areaControllerGroup := authGroup.Group("/area-controllers") { areaControllerGroup.POST("", a.deviceController.CreateAreaController) areaControllerGroup.GET("", a.deviceController.ListAreaControllers) areaControllerGroup.GET("/:id", a.deviceController.GetAreaController) areaControllerGroup.PUT("/:id", a.deviceController.UpdateAreaController) areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController) } a.logger.Info("区域主控相关接口注册成功 (需要认证和审计)") // 计划相关路由组 planGroup := authGroup.Group("/plans") { planGroup.POST("", a.planController.CreatePlan) planGroup.GET("", a.planController.ListPlans) planGroup.GET("/:id", a.planController.GetPlan) planGroup.PUT("/:id", a.planController.UpdatePlan) planGroup.DELETE("/:id", a.planController.DeletePlan) planGroup.POST("/:id/start", a.planController.StartPlan) planGroup.POST("/:id/stop", a.planController.StopPlan) } a.logger.Info("计划相关接口注册成功 (需要认证和审计)") } } // 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 服务器监听失败: %s", err) } }() // 记录服务器已启动的信息 a.logger.Infof("HTTP 服务器正在监听 %s", addr) } // Stop 优雅地停止 HTTP 服务器 // 在停止服务器时,会给一个超时时间,确保正在处理的请求能够完成。 func (a *API) Stop() { // 记录服务器正在关闭的信息 a.logger.Info("正在关闭 HTTP 服务器...") // 创建一个带有 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 服务器关闭失败: %s", err) } // 记录服务器已停止的信息 a.logger.Info("HTTP 服务器已停止。") }