重构 #4
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device" | ||||
| 	"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" | ||||
| @@ -20,18 +21,19 @@ import ( | ||||
|  | ||||
| // 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          // 用户控制器实例 | ||||
| 	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          // 用户控制器实例 | ||||
| 	deviceController *device.Controller        // 设备控制器实例 | ||||
| } | ||||
|  | ||||
| // NewAPI 创建并返回一个新的 API 实例 | ||||
| // 负责初始化 Gin 引擎、设置全局中间件,并注入所有必要的依赖。 | ||||
| func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.UserRepository, tokenService token.TokenService) *API { | ||||
| func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.UserRepository, deviceRepository repository.DeviceRepository, tokenService token.TokenService) *API { | ||||
| 	// 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式) | ||||
| 	// 从配置中获取 Gin 模式 | ||||
| 	gin.SetMode(cfg.Mode) | ||||
| @@ -53,6 +55,8 @@ func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.Us | ||||
| 		config:       cfg, | ||||
| 		// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 | ||||
| 		userController: user.NewController(userRepo, logger, tokenService), | ||||
| 		// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 | ||||
| 		deviceController: device.NewController(deviceRepository, logger), | ||||
| 	} | ||||
|  | ||||
| 	api.setupRoutes() // 设置所有路由 | ||||
| @@ -71,6 +75,16 @@ func (a *API) setupRoutes() { | ||||
| 			userGroup.POST("", a.userController.CreateUser)  // 注册创建用户接口 (POST /api/v1/users) | ||||
| 			userGroup.POST("/login", a.userController.Login) // 注册用户登录接口 (POST /api/v1/users/login) | ||||
| 		} | ||||
|  | ||||
| 		// 设备相关路由组 | ||||
| 		deviceGroup := v1.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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 添加 Swagger UI 路由 | ||||
|   | ||||
							
								
								
									
										224
									
								
								internal/app/controller/device/device_controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								internal/app/controller/device/device_controller.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| package device | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"gorm.io/datatypes" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // Controller 设备控制器,封装了所有与设备相关的业务逻辑 | ||||
| type Controller struct { | ||||
| 	repo   repository.DeviceRepository | ||||
| 	logger *logs.Logger | ||||
| } | ||||
|  | ||||
| // NewController 创建一个新的设备控制器实例 | ||||
| func NewController(repo repository.DeviceRepository, logger *logs.Logger) *Controller { | ||||
| 	return &Controller{ | ||||
| 		repo:   repo, | ||||
| 		logger: logger, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // --- Request & Response Structs --- | ||||
|  | ||||
| // CreateDeviceRequest 定义了创建设备时需要传入的参数 | ||||
| type CreateDeviceRequest struct { | ||||
| 	Name       string               `json:"name" binding:"required"` | ||||
| 	Type       models.DeviceType    `json:"type" binding:"required"` | ||||
| 	SubType    models.DeviceSubType `json:"sub_type,omitempty"` | ||||
| 	ParentID   *uint                `json:"parent_id,omitempty"` | ||||
| 	Location   string               `json:"location,omitempty"` | ||||
| 	Properties datatypes.JSON       `json:"properties,omitempty"` | ||||
| } | ||||
|  | ||||
| // UpdateDeviceRequest 定义了更新设备时需要传入的参数 | ||||
| type UpdateDeviceRequest struct { | ||||
| 	Name       string               `json:"name" binding:"required"` | ||||
| 	Type       models.DeviceType    `json:"type" binding:"required"` | ||||
| 	SubType    models.DeviceSubType `json:"sub_type,omitempty"` | ||||
| 	ParentID   *uint                `json:"parent_id,omitempty"` | ||||
| 	Location   string               `json:"location,omitempty"` | ||||
| 	Properties datatypes.JSON       `json:"properties,omitempty"` | ||||
| } | ||||
|  | ||||
| // --- Controller Methods --- | ||||
|  | ||||
| // CreateDevice godoc | ||||
| // @Summary      创建新设备 | ||||
| // @Description  根据提供的信息创建一个新设备,可以是区域主控或普通设备 | ||||
| // @Tags         设备管理 | ||||
| // @Accept       json | ||||
| // @Produce      json | ||||
| // @Param        device body CreateDeviceRequest true "设备信息" | ||||
| // @Success      200 {object} controller.Response{data=models.Device} "业务码为201代表创建成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)" | ||||
| // @Router       /devices [post] | ||||
| func (c *Controller) CreateDevice(ctx *gin.Context) { | ||||
| 	var req CreateDeviceRequest | ||||
| 	if err := ctx.ShouldBindJSON(&req); err != nil { | ||||
| 		c.logger.Errorf("创建设备: 参数绑定失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusBadRequest, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	device := &models.Device{ | ||||
| 		Name:       req.Name, | ||||
| 		Type:       req.Type, | ||||
| 		SubType:    req.SubType, | ||||
| 		ParentID:   req.ParentID, | ||||
| 		Location:   req.Location, | ||||
| 		Properties: req.Properties, | ||||
| 	} | ||||
|  | ||||
| 	if err := c.repo.Create(device); err != nil { | ||||
| 		c.logger.Errorf("创建设备: 数据库操作失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "创建设备失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusCreated, "设备创建成功", device) | ||||
| } | ||||
|  | ||||
| // GetDevice godoc | ||||
| // @Summary      获取设备信息 | ||||
| // @Description  根据设备ID获取单个设备的详细信息 | ||||
| // @Tags         设备管理 | ||||
| // @Produce      json | ||||
| // @Param        id path string true "设备ID" | ||||
| // @Success      200 {object} controller.Response{data=models.Device} "业务码为200代表获取成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)" | ||||
| // @Router       /devices/{id} [get] | ||||
| func (c *Controller) GetDevice(ctx *gin.Context) { | ||||
| 	deviceID := ctx.Param("id") | ||||
|  | ||||
| 	device, err := c.repo.FindByIDString(deviceID) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 			controller.SendErrorResponse(ctx, http.StatusNotFound, "设备未找到") | ||||
| 			return | ||||
| 		} | ||||
| 		// 检查是否是ID格式错误 | ||||
| 		if strings.Contains(err.Error(), "无效的设备ID格式") { | ||||
| 			controller.SendErrorResponse(ctx, http.StatusBadRequest, err.Error()) | ||||
| 			return | ||||
| 		} | ||||
| 		c.logger.Errorf("获取设备: 数据库操作失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "获取设备信息失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusOK, "获取设备信息成功", device) | ||||
| } | ||||
|  | ||||
| // ListDevices godoc | ||||
| // @Summary      获取设备列表 | ||||
| // @Description  获取系统中所有设备的列表 | ||||
| // @Tags         设备管理 | ||||
| // @Produce      json | ||||
| // @Success      200 {object} controller.Response{data=[]models.Device} "业务码为200代表获取成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如500)" | ||||
| // @Router       /devices [get] | ||||
| func (c *Controller) ListDevices(ctx *gin.Context) { | ||||
| 	devices, err := c.repo.ListAll() | ||||
| 	if err != nil { | ||||
| 		c.logger.Errorf("获取设备列表: 数据库操作失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "获取设备列表失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusOK, "获取设备列表成功", devices) | ||||
| } | ||||
|  | ||||
| // UpdateDevice godoc | ||||
| // @Summary      更新设备信息 | ||||
| // @Description  根据设备ID更新一个已存在的设备信息 | ||||
| // @Tags         设备管理 | ||||
| // @Accept       json | ||||
| // @Produce      json | ||||
| // @Param        id path string true "设备ID" | ||||
| // @Param        device body UpdateDeviceRequest true "要更新的设备信息" | ||||
| // @Success      200 {object} controller.Response{data=models.Device} "业务码为200代表更新成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)" | ||||
| // @Router       /devices/{id} [put] | ||||
| func (c *Controller) UpdateDevice(ctx *gin.Context) { | ||||
| 	deviceID := ctx.Param("id") | ||||
|  | ||||
| 	// 1. 检查设备是否存在 | ||||
| 	existingDevice, err := c.repo.FindByIDString(deviceID) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 			controller.SendErrorResponse(ctx, http.StatusNotFound, "设备未找到") | ||||
| 			return | ||||
| 		} | ||||
| 		if strings.Contains(err.Error(), "无效的设备ID格式") { | ||||
| 			controller.SendErrorResponse(ctx, http.StatusBadRequest, err.Error()) | ||||
| 			return | ||||
| 		} | ||||
| 		c.logger.Errorf("更新设备: 查找设备失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "更新设备失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 2. 绑定请求参数 | ||||
| 	var req UpdateDeviceRequest | ||||
| 	if err := ctx.ShouldBindJSON(&req); err != nil { | ||||
| 		c.logger.Errorf("更新设备: 参数绑定失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusBadRequest, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 3. 更新字段 | ||||
| 	existingDevice.Name = req.Name | ||||
| 	existingDevice.Type = req.Type | ||||
| 	existingDevice.SubType = req.SubType | ||||
| 	existingDevice.ParentID = req.ParentID | ||||
| 	existingDevice.Location = req.Location | ||||
| 	existingDevice.Properties = req.Properties | ||||
|  | ||||
| 	// 4. 保存到数据库 | ||||
| 	if err := c.repo.Update(existingDevice); err != nil { | ||||
| 		c.logger.Errorf("更新设备: 数据库操作失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "更新设备失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusOK, "设备更新成功", existingDevice) | ||||
| } | ||||
|  | ||||
| // DeleteDevice godoc | ||||
| // @Summary      删除设备 | ||||
| // @Description  根据设备ID删除一个设备(软删除) | ||||
| // @Tags         设备管理 | ||||
| // @Produce      json | ||||
| // @Param        id path string true "设备ID" | ||||
| // @Success      200 {object} controller.Response "业务码为200代表删除成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)" | ||||
| // @Router       /devices/{id} [delete] | ||||
| func (c *Controller) DeleteDevice(ctx *gin.Context) { | ||||
| 	deviceID := ctx.Param("id") | ||||
|  | ||||
| 	// 我们需要先将字符串ID转换为uint,因为Delete方法需要uint类型 | ||||
| 	idUint, err := strconv.ParseUint(deviceID, 10, 64) | ||||
| 	if err != nil { | ||||
| 		controller.SendErrorResponse(ctx, http.StatusBadRequest, "无效的设备ID格式") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := c.repo.Delete(uint(idUint)); err != nil { | ||||
| 		c.logger.Errorf("删除设备: 数据库操作失败: %v", err) | ||||
| 		controller.SendErrorResponse(ctx, http.StatusInternalServerError, "删除设备失败") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusOK, "设备删除成功", nil) | ||||
| } | ||||
| @@ -60,10 +60,8 @@ type LoginResponse struct { | ||||
| // @Accept       json | ||||
| // @Produce      json | ||||
| // @Param        user body CreateUserRequest true "用户信息" | ||||
| // @Success      200 {object} controller.Response{data=CreateUserResponse} "用户创建成功" | ||||
| // @Failure      400 {object} controller.Response "请求参数错误" | ||||
| // @Failure      409 {object} controller.Response "用户名已存在" | ||||
| // @Failure      500 {object} controller.Response "服务器内部错误" | ||||
| // @Success      200 {object} controller.Response{data=CreateUserResponse} "业务码为201代表创建成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 409, 500)" | ||||
| // @Router       /users [post] | ||||
| func (c *Controller) CreateUser(ctx *gin.Context) { | ||||
| 	var req CreateUserRequest | ||||
| @@ -93,7 +91,7 @@ func (c *Controller) CreateUser(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	controller.SendResponse(ctx, http.StatusOK, "用户创建成功", CreateUserResponse{ | ||||
| 	controller.SendResponse(ctx, http.StatusCreated, "用户创建成功", CreateUserResponse{ | ||||
| 		Username: user.Username, | ||||
| 		ID:       user.ID, | ||||
| 	}) | ||||
| @@ -106,10 +104,8 @@ func (c *Controller) CreateUser(ctx *gin.Context) { | ||||
| // @Accept       json | ||||
| // @Produce      json | ||||
| // @Param        credentials body LoginRequest true "登录凭证" | ||||
| // @Success      200 {object} controller.Response{data=LoginResponse} "登录成功" | ||||
| // @Failure      400 {object} controller.Response "请求参数错误" | ||||
| // @Failure      401 {object} controller.Response "用户名或密码不正确" | ||||
| // @Failure      500 {object} controller.Response "服务器内部错误" | ||||
| // @Success      200 {object} controller.Response{data=LoginResponse} "业务码为200代表登录成功" | ||||
| // @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 401, 500)" | ||||
| // @Router       /users/login [post] | ||||
| func (c *Controller) Login(ctx *gin.Context) { | ||||
| 	var req LoginRequest | ||||
|   | ||||
| @@ -46,16 +46,19 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 	// 4. 初始化任务执行器 | ||||
| 	executor := task.NewExecutor(cfg.Heartbeat.Concurrency, logger) | ||||
|  | ||||
| 	// 5. 初始化用户仓库 | ||||
| 	userRepo := repository.NewGormUserRepository(storage.GetDB()) | ||||
|  | ||||
| 	// 6. 初始化 Token 服务 | ||||
| 	// 5. 初始化 Token 服务 | ||||
| 	tokenService := token.NewTokenService([]byte(cfg.App.JWTSecret)) | ||||
|  | ||||
| 	// 7. 初始化 API 服务器 | ||||
| 	apiServer := api.NewAPI(cfg.Server, logger, userRepo, tokenService) | ||||
| 	// 6. 初始化用户仓库 | ||||
| 	userRepo := repository.NewGormUserRepository(storage.GetDB()) | ||||
|  | ||||
| 	// 8. 组装 Application 对象 | ||||
| 	// 7. 初始化设备仓库 | ||||
| 	deviceRepo := repository.NewGormDeviceRepository(storage.GetDB()) | ||||
|  | ||||
| 	// 8. 初始化 API 服务器 | ||||
| 	apiServer := api.NewAPI(cfg.Server, logger, userRepo, deviceRepo, tokenService) | ||||
|  | ||||
| 	// 9. 组装 Application 对象 | ||||
| 	app := &Application{ | ||||
| 		Config:   cfg, | ||||
| 		Logger:   logger, | ||||
|   | ||||
| @@ -20,6 +20,9 @@ type DeviceRepository interface { | ||||
| 	// FindByIDString 根据字符串形式的主键 ID 查找设备,方便控制器调用 | ||||
| 	FindByIDString(id string) (*models.Device, error) | ||||
|  | ||||
| 	// ListAll 获取所有设备的列表 | ||||
| 	ListAll() ([]*models.Device, error) | ||||
|  | ||||
| 	// ListByParentID 根据父级 ID 列出所有子设备。 | ||||
| 	// 如果 parentID 为 nil,则列出所有顶层设备(如区域主控)。 | ||||
| 	ListByParentID(parentID *uint) ([]*models.Device, error) | ||||
| @@ -67,6 +70,15 @@ func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error) | ||||
| 	return r.FindByID(uint(idInt)) | ||||
| } | ||||
|  | ||||
| // ListAll 获取所有设备的列表 | ||||
| func (r *gormDeviceRepository) ListAll() ([]*models.Device, error) { | ||||
| 	var devices []*models.Device | ||||
| 	if err := r.db.Find(&devices).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return devices, nil | ||||
| } | ||||
|  | ||||
| // ListByParentID 根据父级 ID 列出所有子设备 | ||||
| func (r *gormDeviceRepository) ListByParentID(parentID *uint) ([]*models.Device, error) { | ||||
| 	var devices []*models.Device | ||||
|   | ||||
		Reference in New Issue
	
	Block a user