初步实现device_controller

This commit is contained in:
2025-09-12 17:18:14 +08:00
parent 4a78aa1c20
commit fe9b0db985
5 changed files with 273 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"time" "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/controller/user"
"git.huangwc.com/pig/pig-farm-controller/internal/app/service/token" "git.huangwc.com/pig/pig-farm-controller/internal/app/service/token"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config" "git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
@@ -20,18 +21,19 @@ import (
// API 结构体定义了 HTTP 服务器及其依赖 // API 结构体定义了 HTTP 服务器及其依赖
type API struct { type API struct {
engine *gin.Engine // Gin 引擎实例,用于处理 HTTP 请求 engine *gin.Engine // Gin 引擎实例,用于处理 HTTP 请求
logger *logs.Logger // 日志记录器,用于输出日志信息 logger *logs.Logger // 日志记录器,用于输出日志信息
userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作 userRepo repository.UserRepository // 用户数据仓库接口,用于用户数据操作
tokenService token.TokenService // Token 服务接口,用于 JWT token 的生成和解析 tokenService token.TokenService // Token 服务接口,用于 JWT token 的生成和解析
httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务 httpServer *http.Server // 标准库的 HTTP 服务器实例,用于启动和停止服务
config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig config config.ServerConfig // API 服务器的配置,使用 infra/config 包中的 ServerConfig
userController *user.Controller // 用户控制器实例 userController *user.Controller // 用户控制器实例
deviceController *device.Controller // 设备控制器实例
} }
// NewAPI 创建并返回一个新的 API 实例 // NewAPI 创建并返回一个新的 API 实例
// 负责初始化 Gin 引擎、设置全局中间件,并注入所有必要的依赖。 // 负责初始化 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.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式)
// 从配置中获取 Gin 模式 // 从配置中获取 Gin 模式
gin.SetMode(cfg.Mode) gin.SetMode(cfg.Mode)
@@ -53,6 +55,8 @@ func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.Us
config: cfg, config: cfg,
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
userController: user.NewController(userRepo, logger, tokenService), userController: user.NewController(userRepo, logger, tokenService),
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
deviceController: device.NewController(deviceRepository, logger),
} }
api.setupRoutes() // 设置所有路由 api.setupRoutes() // 设置所有路由
@@ -71,6 +75,16 @@ func (a *API) setupRoutes() {
userGroup.POST("", a.userController.CreateUser) // 注册创建用户接口 (POST /api/v1/users) userGroup.POST("", a.userController.CreateUser) // 注册创建用户接口 (POST /api/v1/users)
userGroup.POST("/login", a.userController.Login) // 注册用户登录接口 (POST /api/v1/users/login) 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 路由 // 添加 Swagger UI 路由

View 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)
}

View File

@@ -60,10 +60,8 @@ type LoginResponse struct {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param user body CreateUserRequest true "用户信息" // @Param user body CreateUserRequest true "用户信息"
// @Success 200 {object} controller.Response{data=CreateUserResponse} "用户创建成功" // @Success 200 {object} controller.Response{data=CreateUserResponse} "业务码为201代表创建成功"
// @Failure 400 {object} controller.Response "请求参数错误" // @Failure 200 {object} controller.Response "业务失败具体错误码和信息见响应体例如400, 409, 500"
// @Failure 409 {object} controller.Response "用户名已存在"
// @Failure 500 {object} controller.Response "服务器内部错误"
// @Router /users [post] // @Router /users [post]
func (c *Controller) CreateUser(ctx *gin.Context) { func (c *Controller) CreateUser(ctx *gin.Context) {
var req CreateUserRequest var req CreateUserRequest
@@ -93,7 +91,7 @@ func (c *Controller) CreateUser(ctx *gin.Context) {
return return
} }
controller.SendResponse(ctx, http.StatusOK, "用户创建成功", CreateUserResponse{ controller.SendResponse(ctx, http.StatusCreated, "用户创建成功", CreateUserResponse{
Username: user.Username, Username: user.Username,
ID: user.ID, ID: user.ID,
}) })
@@ -106,10 +104,8 @@ func (c *Controller) CreateUser(ctx *gin.Context) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param credentials body LoginRequest true "登录凭证" // @Param credentials body LoginRequest true "登录凭证"
// @Success 200 {object} controller.Response{data=LoginResponse} "登录成功" // @Success 200 {object} controller.Response{data=LoginResponse} "业务码为200代表登录成功"
// @Failure 400 {object} controller.Response "请求参数错误" // @Failure 200 {object} controller.Response "业务失败具体错误码和信息见响应体例如400, 401, 500"
// @Failure 401 {object} controller.Response "用户名或密码不正确"
// @Failure 500 {object} controller.Response "服务器内部错误"
// @Router /users/login [post] // @Router /users/login [post]
func (c *Controller) Login(ctx *gin.Context) { func (c *Controller) Login(ctx *gin.Context) {
var req LoginRequest var req LoginRequest

View File

@@ -46,16 +46,19 @@ func NewApplication(configPath string) (*Application, error) {
// 4. 初始化任务执行器 // 4. 初始化任务执行器
executor := task.NewExecutor(cfg.Heartbeat.Concurrency, logger) executor := task.NewExecutor(cfg.Heartbeat.Concurrency, logger)
// 5. 初始化用户仓库 // 5. 初始化 Token 服务
userRepo := repository.NewGormUserRepository(storage.GetDB())
// 6. 初始化 Token 服务
tokenService := token.NewTokenService([]byte(cfg.App.JWTSecret)) tokenService := token.NewTokenService([]byte(cfg.App.JWTSecret))
// 7. 初始化 API 服务器 // 6. 初始化用户仓库
apiServer := api.NewAPI(cfg.Server, logger, userRepo, tokenService) 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{ app := &Application{
Config: cfg, Config: cfg,
Logger: logger, Logger: logger,

View File

@@ -20,6 +20,9 @@ type DeviceRepository interface {
// FindByIDString 根据字符串形式的主键 ID 查找设备,方便控制器调用 // FindByIDString 根据字符串形式的主键 ID 查找设备,方便控制器调用
FindByIDString(id string) (*models.Device, error) FindByIDString(id string) (*models.Device, error)
// ListAll 获取所有设备的列表
ListAll() ([]*models.Device, error)
// ListByParentID 根据父级 ID 列出所有子设备。 // ListByParentID 根据父级 ID 列出所有子设备。
// 如果 parentID 为 nil则列出所有顶层设备如区域主控 // 如果 parentID 为 nil则列出所有顶层设备如区域主控
ListByParentID(parentID *uint) ([]*models.Device, error) ListByParentID(parentID *uint) ([]*models.Device, error)
@@ -67,6 +70,15 @@ func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error)
return r.FindByID(uint(idInt)) 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 列出所有子设备 // ListByParentID 根据父级 ID 列出所有子设备
func (r *gormDeviceRepository) ListByParentID(parentID *uint) ([]*models.Device, error) { func (r *gormDeviceRepository) ListByParentID(parentID *uint) ([]*models.Device, error) {
var devices []*models.Device var devices []*models.Device