Files
pig-farm-controller/internal/app/controller/device/device_controller.go
2025-09-20 17:11:04 +08:00

318 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package device
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"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/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 DTOs ---
// 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 map[string]interface{} `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 map[string]interface{} `json:"properties,omitempty"`
}
// --- Response DTOs ---
// DeviceResponse 定义了返回给客户端的单个设备信息的结构
type DeviceResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
Type models.DeviceType `json:"type"`
SubType models.DeviceSubType `json:"sub_type"`
ParentID *uint `json:"parent_id"`
Location string `json:"location"`
Properties map[string]interface{} `json:"properties"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// --- DTO 转换函数 ---
// newDeviceResponse 从数据库模型创建一个新的设备响应 DTO
func newDeviceResponse(device *models.Device) (*DeviceResponse, error) {
if device == nil {
return nil, nil
}
var props map[string]interface{}
if len(device.Properties) > 0 && string(device.Properties) != "null" {
if err := json.Unmarshal(device.Properties, &props); err != nil {
return nil, fmt.Errorf("解析设备属性失败 (ID: %d): %w", device.ID, err)
}
}
return &DeviceResponse{
ID: device.ID,
Name: device.Name,
Type: device.Type,
SubType: device.SubType,
ParentID: device.ParentID,
Location: device.Location,
Properties: props,
CreatedAt: device.CreatedAt.Format(time.RFC3339),
UpdatedAt: device.UpdatedAt.Format(time.RFC3339),
}, nil
}
// newListDeviceResponse 从数据库模型切片创建一个新的设备列表响应 DTO 切片
func newListDeviceResponse(devices []*models.Device) ([]*DeviceResponse, error) {
list := make([]*DeviceResponse, 0, len(devices))
for _, device := range devices {
resp, err := newDeviceResponse(device)
if err != nil {
return nil, err
}
list = append(list, resp)
}
return list, nil
}
// --- Controller Methods ---
// CreateDevice godoc
// @Summary 创建新设备
// @Description 根据提供的信息创建一个新设备
// @Tags 设备管理
// @Accept json
// @Produce json
// @Param device body CreateDeviceRequest true "设备信息"
// @Success 200 {object} controller.Response{data=DeviceResponse}
// @Router /api/v1/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, controller.CodeBadRequest, err.Error())
return
}
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
c.logger.Errorf("创建设备: 序列化属性失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
return
}
device := &models.Device{
Name: req.Name,
Type: req.Type,
SubType: req.SubType,
ParentID: req.ParentID,
Location: req.Location,
Properties: propertiesJSON,
}
if err := c.repo.Create(device); err != nil {
c.logger.Errorf("创建设备: 数据库操作失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "创建设备失败")
return
}
resp, err := newDeviceResponse(device)
if err != nil {
c.logger.Errorf("创建设备: 序列化响应失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "设备创建成功,但响应生成失败")
return
}
controller.SendResponse(ctx, controller.CodeCreated, "设备创建成功", resp)
}
// GetDevice godoc
// @Summary 获取设备信息
// @Description 根据设备ID获取单个设备的详细信息
// @Tags 设备管理
// @Produce json
// @Param id path string true "设备ID"
// @Success 200 {object} controller.Response{data=DeviceResponse}
// @Router /api/v1/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, controller.CodeNotFound, "设备未找到")
return
}
if strings.Contains(err.Error(), "无效的设备ID格式") {
controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
return
}
c.logger.Errorf("获取设备: 数据库操作失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取设备信息失败")
return
}
resp, err := newDeviceResponse(device)
if err != nil {
c.logger.Errorf("获取设备: 序列化响应失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取设备信息失败: 内部数据格式错误")
return
}
controller.SendResponse(ctx, controller.CodeSuccess, "获取设备信息成功", resp)
}
// ListDevices godoc
// @Summary 获取设备列表
// @Description 获取系统中所有设备的列表
// @Tags 设备管理
// @Produce json
// @Success 200 {object} controller.Response{data=[]DeviceResponse}
// @Router /api/v1/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, controller.CodeInternalError, "获取设备列表失败")
return
}
resp, err := newListDeviceResponse(devices)
if err != nil {
c.logger.Errorf("获取设备列表: 序列化响应失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取设备列表失败: 内部数据格式错误")
return
}
controller.SendResponse(ctx, controller.CodeSuccess, "获取设备列表成功", resp)
}
// 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=DeviceResponse}
// @Router /api/v1/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, controller.CodeNotFound, "设备未找到")
return
}
if strings.Contains(err.Error(), "无效的设备ID格式") {
controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
return
}
c.logger.Errorf("更新设备: 查找设备失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "更新设备失败")
return
}
// 2. 绑定请求参数
var req UpdateDeviceRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("更新设备: 参数绑定失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
return
}
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
c.logger.Errorf("更新设备: 序列化属性失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
return
}
// 3. 更新从数据库中查出的现有设备对象的字段
existingDevice.Name = req.Name
existingDevice.Type = req.Type
existingDevice.SubType = req.SubType
existingDevice.ParentID = req.ParentID
existingDevice.Location = req.Location
existingDevice.Properties = propertiesJSON
// 4. 将修改后的 existingDevice 对象保存回数据库
if err := c.repo.Update(existingDevice); err != nil {
c.logger.Errorf("更新设备: 数据库操作失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "更新设备失败")
return
}
resp, err := newDeviceResponse(existingDevice)
if err != nil {
c.logger.Errorf("更新设备: 序列化响应失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "设备更新成功,但响应生成失败")
return
}
controller.SendResponse(ctx, controller.CodeSuccess, "设备更新成功", resp)
}
// DeleteDevice godoc
// @Summary 删除设备
// @Description 根据设备ID删除一个设备软删除
// @Tags 设备管理
// @Produce json
// @Param id path string true "设备ID"
// @Success 200 {object} controller.Response
// @Router /api/v1/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, controller.CodeBadRequest, "无效的设备ID格式")
return
}
if err := c.repo.Delete(uint(idUint)); err != nil {
c.logger.Errorf("删除设备: 数据库操作失败: %v", err)
controller.SendErrorResponse(ctx, controller.CodeInternalError, "删除设备失败")
return
}
controller.SendResponse(ctx, controller.CodeSuccess, "设备删除成功", nil)
}