diff --git a/internal/app/controller/device/device_controller.go b/internal/app/controller/device/device_controller.go index ddbbc25..7d49124 100644 --- a/internal/app/controller/device/device_controller.go +++ b/internal/app/controller/device/device_controller.go @@ -3,12 +3,11 @@ 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/app/dto" "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" @@ -34,243 +33,11 @@ func NewController( return &Controller{ deviceRepo: deviceRepo, areaControllerRepo: areaControllerRepo, - deviceTemplateRepo: deviceTemplateRepo, // 初始化设备模板仓库 + deviceTemplateRepo: deviceTemplateRepo, logger: logger, } } -// --- Request DTOs --- - -// CreateDeviceRequest 定义了创建设备时需要传入的参数 -type CreateDeviceRequest struct { - Name string `json:"name" binding:"required"` - DeviceTemplateID uint `json:"device_template_id" binding:"required"` - AreaControllerID uint `json:"area_controller_id" binding:"required"` - Location string `json:"location,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` -} - -// UpdateDeviceRequest 定义了更新设备时需要传入的参数 -type UpdateDeviceRequest struct { - Name string `json:"name" binding:"required"` - DeviceTemplateID uint `json:"device_template_id" binding:"required"` - AreaControllerID uint `json:"area_controller_id" binding:"required"` - Location string `json:"location,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` -} - -// CreateAreaControllerRequest 定义了创建区域主控时需要传入的参数 -type CreateAreaControllerRequest struct { - Name string `json:"name" binding:"required"` - NetworkID string `json:"network_id" binding:"required"` - Location string `json:"location,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` -} - -// UpdateAreaControllerRequest 定义了更新区域主控时需要传入的参数 -type UpdateAreaControllerRequest struct { - Name string `json:"name" binding:"required"` - NetworkID string `json:"network_id" binding:"required"` - Location string `json:"location,omitempty"` - Properties map[string]interface{} `json:"properties,omitempty"` -} - -// CreateDeviceTemplateRequest 定义了创建设备模板时需要传入的参数 -type CreateDeviceTemplateRequest struct { - Name string `json:"name" binding:"required"` - Manufacturer string `json:"manufacturer,omitempty"` - Description string `json:"description,omitempty"` - Category models.DeviceCategory `json:"category" binding:"required"` - Commands map[string]interface{} `json:"commands" binding:"required"` - Values []models.ValueDescriptor `json:"values,omitempty"` -} - -// UpdateDeviceTemplateRequest 定义了更新设备模板时需要传入的参数 -type UpdateDeviceTemplateRequest struct { - Name string `json:"name" binding:"required"` - Manufacturer string `json:"manufacturer,omitempty"` - Description string `json:"description,omitempty"` - Category models.DeviceCategory `json:"category" binding:"required"` - Commands map[string]interface{} `json:"commands" binding:"required"` - Values []models.ValueDescriptor `json:"values,omitempty"` -} - -// --- Response DTOs --- - -// DeviceResponse 定义了返回给客户端的单个设备信息的结构 -type DeviceResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - DeviceTemplateID uint `json:"device_template_id"` - DeviceTemplateName string `json:"device_template_name"` - AreaControllerID uint `json:"area_controller_id"` - AreaControllerName string `json:"area_controller_name"` - Location string `json:"location"` - Properties map[string]interface{} `json:"properties"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} - -// AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构 -type AreaControllerResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - NetworkID string `json:"network_id"` - Location string `json:"location"` - Status string `json:"status"` - Properties map[string]interface{} `json:"properties"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} - -// DeviceTemplateResponse 定义了返回给客户端的单个设备模板信息的结构 -type DeviceTemplateResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - Manufacturer string `json:"manufacturer"` - Description string `json:"description"` - Category models.DeviceCategory `json:"category"` - Commands map[string]interface{} `json:"commands"` - Values []models.ValueDescriptor `json:"values"` - 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 := device.ParseProperties(&props); err != nil { - return nil, fmt.Errorf("解析设备属性失败 (ID: %d): %w", device.ID, err) - } - } - - // 确保 DeviceTemplate 和 AreaController 已预加载 - deviceTemplateName := "" - if device.DeviceTemplate.ID != 0 { - deviceTemplateName = device.DeviceTemplate.Name - } - - areaControllerName := "" - if device.AreaController.ID != 0 { - areaControllerName = device.AreaController.Name - } - - return &DeviceResponse{ - ID: device.ID, - Name: device.Name, - DeviceTemplateID: device.DeviceTemplateID, - DeviceTemplateName: deviceTemplateName, - AreaControllerID: device.AreaControllerID, - AreaControllerName: areaControllerName, - 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 -} - -// newAreaControllerResponse 从数据库模型创建一个新的区域主控响应 DTO -func newAreaControllerResponse(ac *models.AreaController) (*AreaControllerResponse, error) { - if ac == nil { - return nil, nil - } - - var props map[string]interface{} - if len(ac.Properties) > 0 && string(ac.Properties) != "null" { - if err := json.Unmarshal(ac.Properties, &props); err != nil { - return nil, fmt.Errorf("解析区域主控属性失败 (ID: %d): %w", ac.ID, err) - } - } - - return &AreaControllerResponse{ - ID: ac.ID, - Name: ac.Name, - NetworkID: ac.NetworkID, - Location: ac.Location, - Status: ac.Status, - Properties: props, - CreatedAt: ac.CreatedAt.Format(time.RFC3339), - UpdatedAt: ac.UpdatedAt.Format(time.RFC3339), - }, nil -} - -// newListAreaControllerResponse 从数据库模型切片创建一个新的区域主控列表响应 DTO 切片 -func newListAreaControllerResponse(acs []*models.AreaController) ([]*AreaControllerResponse, error) { - list := make([]*AreaControllerResponse, 0, len(acs)) - for _, ac := range acs { - resp, err := newAreaControllerResponse(ac) - if err != nil { - return nil, err - } - list = append(list, resp) - } - return list, nil -} - -// newDeviceTemplateResponse 从数据库模型创建一个新的设备模板响应 DTO -func newDeviceTemplateResponse(dt *models.DeviceTemplate) (*DeviceTemplateResponse, error) { - if dt == nil { - return nil, nil - } - - var commands map[string]interface{} - if err := dt.ParseCommands(&commands); err != nil { - return nil, fmt.Errorf("解析设备模板命令失败 (ID: %d): %w", dt.ID, err) - } - - var values []models.ValueDescriptor - if dt.Category == models.CategorySensor { - if err := dt.ParseValues(&values); err != nil { - return nil, fmt.Errorf("解析设备模板值描述符失败 (ID: %d): %w", dt.ID, err) - } - } - - return &DeviceTemplateResponse{ - ID: dt.ID, - Name: dt.Name, - Manufacturer: dt.Manufacturer, - Description: dt.Description, - Category: dt.Category, - Commands: commands, - Values: values, - CreatedAt: dt.CreatedAt.Format(time.RFC3339), - UpdatedAt: dt.UpdatedAt.Format(time.RFC3339), - }, nil -} - -// newListDeviceTemplateResponse 从数据库模型切片创建一个新的设备模板列表响应 DTO 切片 -func newListDeviceTemplateResponse(dts []*models.DeviceTemplate) ([]*DeviceTemplateResponse, error) { - list := make([]*DeviceTemplateResponse, 0, len(dts)) - for _, dt := range dts { - resp, err := newDeviceTemplateResponse(dt) - if err != nil { - return nil, err - } - list = append(list, resp) - } - return list, nil -} - // --- Controller Methods: Devices --- // CreateDevice godoc @@ -279,12 +46,12 @@ func newListDeviceTemplateResponse(dts []*models.DeviceTemplate) ([]*DeviceTempl // @Tags 设备管理 // @Accept json // @Produce json -// @Param device body CreateDeviceRequest true "设备信息" -// @Success 200 {object} controller.Response{data=DeviceResponse} +// @Param device body dto.CreateDeviceRequest true "设备信息" +// @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices [post] func (c *Controller) CreateDevice(ctx *gin.Context) { const actionType = "创建设备" - var req CreateDeviceRequest + var req dto.CreateDeviceRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -325,9 +92,9 @@ func (c *Controller) CreateDevice(ctx *gin.Context) { return } - resp, err := newDeviceResponse(createdDevice) + resp, err := dto.NewDeviceResponse(createdDevice) if err != nil { - c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err) + c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, createdDevice) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但响应生成失败", actionType, "响应序列化失败", createdDevice) return } @@ -342,7 +109,7 @@ func (c *Controller) CreateDevice(ctx *gin.Context) { // @Tags 设备管理 // @Produce json // @Param id path string true "设备ID" -// @Success 200 {object} controller.Response{data=DeviceResponse} +// @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices/{id} [get] func (c *Controller) GetDevice(ctx *gin.Context) { const actionType = "获取设备" @@ -371,7 +138,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) { return } - resp, err := newDeviceResponse(device) + resp, err := dto.NewDeviceResponse(device) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, device) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备信息失败: 内部数据格式错误", actionType, "响应序列化失败", device) @@ -387,7 +154,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) { // @Description 获取系统中所有设备的列表 // @Tags 设备管理 // @Produce json -// @Success 200 {object} controller.Response{data=[]DeviceResponse} +// @Success 200 {object} controller.Response{data=[]dto.DeviceResponse} // @Router /api/v1/devices [get] func (c *Controller) ListDevices(ctx *gin.Context) { const actionType = "获取设备列表" @@ -398,7 +165,7 @@ func (c *Controller) ListDevices(ctx *gin.Context) { return } - resp, err := newListDeviceResponse(devices) + resp, err := dto.NewListDeviceResponse(devices) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Devices: %+v", actionType, err, devices) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: 内部数据格式错误", actionType, "响应序列化失败", devices) @@ -416,8 +183,8 @@ func (c *Controller) ListDevices(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path string true "设备ID" -// @Param device body UpdateDeviceRequest true "要更新的设备信息" -// @Success 200 {object} controller.Response{data=DeviceResponse} +// @Param device body dto.UpdateDeviceRequest true "要更新的设备信息" +// @Success 200 {object} controller.Response{data=dto.DeviceResponse} // @Router /api/v1/devices/{id} [put] func (c *Controller) UpdateDevice(ctx *gin.Context) { const actionType = "更新设备" @@ -440,7 +207,7 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) { return } - var req UpdateDeviceRequest + var req dto.UpdateDeviceRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -479,7 +246,7 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) { return } - resp, err := newDeviceResponse(updatedDevice) + resp, err := dto.NewDeviceResponse(updatedDevice) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, updatedDevice) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但响应生成失败", actionType, "响应序列化失败", updatedDevice) @@ -539,12 +306,12 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) { // @Tags 区域主控管理 // @Accept json // @Produce json -// @Param areaController body CreateAreaControllerRequest true "区域主控信息" -// @Success 200 {object} controller.Response{data=AreaControllerResponse} +// @Param areaController body dto.CreateAreaControllerRequest true "区域主控信息" +// @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers [post] func (c *Controller) CreateAreaController(ctx *gin.Context) { const actionType = "创建区域主控" - var req CreateAreaControllerRequest + var req dto.CreateAreaControllerRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -577,7 +344,7 @@ func (c *Controller) CreateAreaController(ctx *gin.Context) { return } - resp, err := newAreaControllerResponse(ac) + resp, err := dto.NewAreaControllerResponse(ac) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控创建成功,但响应生成失败", actionType, "响应序列化失败", ac) @@ -594,7 +361,7 @@ func (c *Controller) CreateAreaController(ctx *gin.Context) { // @Tags 区域主控管理 // @Produce json // @Param id path string true "区域主控ID" -// @Success 200 {object} controller.Response{data=AreaControllerResponse} +// @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers/{id} [get] func (c *Controller) GetAreaController(ctx *gin.Context) { const actionType = "获取区域主控" @@ -619,7 +386,7 @@ func (c *Controller) GetAreaController(ctx *gin.Context) { return } - resp, err := newAreaControllerResponse(ac) + resp, err := dto.NewAreaControllerResponse(ac) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, ac) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: 内部数据格式错误", actionType, "响应序列化失败", ac) @@ -635,7 +402,7 @@ func (c *Controller) GetAreaController(ctx *gin.Context) { // @Description 获取系统中所有区域主控的列表 // @Tags 区域主控管理 // @Produce json -// @Success 200 {object} controller.Response{data=[]AreaControllerResponse} +// @Success 200 {object} controller.Response{data=[]dto.AreaControllerResponse} // @Router /api/v1/area-controllers [get] func (c *Controller) ListAreaControllers(ctx *gin.Context) { const actionType = "获取区域主控列表" @@ -646,7 +413,7 @@ func (c *Controller) ListAreaControllers(ctx *gin.Context) { return } - resp, err := newListAreaControllerResponse(acs) + resp, err := dto.NewListAreaControllerResponse(acs) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, AreaControllers: %+v", actionType, err, acs) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: 内部数据格式错误", actionType, "响应序列化失败", acs) @@ -664,8 +431,8 @@ func (c *Controller) ListAreaControllers(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path string true "区域主控ID" -// @Param areaController body UpdateAreaControllerRequest true "要更新的区域主控信息" -// @Success 200 {object} controller.Response{data=AreaControllerResponse} +// @Param areaController body dto.UpdateAreaControllerRequest true "要更新的区域主控信息" +// @Success 200 {object} controller.Response{data=dto.AreaControllerResponse} // @Router /api/v1/area-controllers/{id} [put] func (c *Controller) UpdateAreaController(ctx *gin.Context) { const actionType = "更新区域主控" @@ -690,7 +457,7 @@ func (c *Controller) UpdateAreaController(ctx *gin.Context) { return } - var req UpdateAreaControllerRequest + var req dto.UpdateAreaControllerRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -721,7 +488,7 @@ func (c *Controller) UpdateAreaController(ctx *gin.Context) { return } - resp, err := newAreaControllerResponse(existingAC) + resp, err := dto.NewAreaControllerResponse(existingAC) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, existingAC) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控更新成功,但响应生成失败", actionType, "响应序列化失败", existingAC) @@ -781,12 +548,12 @@ func (c *Controller) DeleteAreaController(ctx *gin.Context) { // @Tags 设备模板管理 // @Accept json // @Produce json -// @Param deviceTemplate body CreateDeviceTemplateRequest true "设备模板信息" -// @Success 200 {object} controller.Response{data=DeviceTemplateResponse} +// @Param deviceTemplate body dto.CreateDeviceTemplateRequest true "设备模板信息" +// @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates [post] func (c *Controller) CreateDeviceTemplate(ctx *gin.Context) { const actionType = "创建设备模板" - var req CreateDeviceTemplateRequest + var req dto.CreateDeviceTemplateRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -828,7 +595,7 @@ func (c *Controller) CreateDeviceTemplate(ctx *gin.Context) { return } - resp, err := newDeviceTemplateResponse(deviceTemplate) + resp, err := dto.NewDeviceTemplateResponse(deviceTemplate) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备模板创建成功,但响应生成失败", actionType, "响应序列化失败", deviceTemplate) @@ -845,7 +612,7 @@ func (c *Controller) CreateDeviceTemplate(ctx *gin.Context) { // @Tags 设备模板管理 // @Produce json // @Param id path string true "设备模板ID" -// @Success 200 {object} controller.Response{data=DeviceTemplateResponse} +// @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates/{id} [get] func (c *Controller) GetDeviceTemplate(ctx *gin.Context) { const actionType = "获取设备模板" @@ -870,7 +637,7 @@ func (c *Controller) GetDeviceTemplate(ctx *gin.Context) { return } - resp, err := newDeviceTemplateResponse(deviceTemplate) + resp, err := dto.NewDeviceTemplateResponse(deviceTemplate) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplate: %+v", actionType, err, deviceTemplate) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板信息失败: 内部数据格式错误", actionType, "响应序列化失败", deviceTemplate) @@ -886,7 +653,7 @@ func (c *Controller) GetDeviceTemplate(ctx *gin.Context) { // @Description 获取系统中所有设备模板的列表 // @Tags 设备模板管理 // @Produce json -// @Success 200 {object} controller.Response{data=[]DeviceTemplateResponse} +// @Success 200 {object} controller.Response{data=[]dto.DeviceTemplateResponse} // @Router /api/v1/device-templates [get] func (c *Controller) ListDeviceTemplates(ctx *gin.Context) { const actionType = "获取设备模板列表" @@ -897,7 +664,7 @@ func (c *Controller) ListDeviceTemplates(ctx *gin.Context) { return } - resp, err := newListDeviceTemplateResponse(deviceTemplates) + resp, err := dto.NewListDeviceTemplateResponse(deviceTemplates) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplates: %+v", actionType, err, deviceTemplates) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板列表失败: 内部数据格式错误", actionType, "响应序列化失败", deviceTemplates) @@ -915,8 +682,8 @@ func (c *Controller) ListDeviceTemplates(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path string true "设备模板ID" -// @Param deviceTemplate body UpdateDeviceTemplateRequest true "要更新的设备模板信息" -// @Success 200 {object} controller.Response{data=DeviceTemplateResponse} +// @Param deviceTemplate body dto.UpdateDeviceTemplateRequest true "要更新的设备模板信息" +// @Success 200 {object} controller.Response{data=dto.DeviceTemplateResponse} // @Router /api/v1/device-templates/{id} [put] func (c *Controller) UpdateDeviceTemplate(ctx *gin.Context) { const actionType = "更新设备模板" @@ -941,7 +708,7 @@ func (c *Controller) UpdateDeviceTemplate(ctx *gin.Context) { return } - var req UpdateDeviceTemplateRequest + var req dto.UpdateDeviceTemplateRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -981,7 +748,7 @@ func (c *Controller) UpdateDeviceTemplate(ctx *gin.Context) { return } - resp, err := newDeviceTemplateResponse(existingDeviceTemplate) + resp, err := dto.NewDeviceTemplateResponse(existingDeviceTemplate) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplate: %+v", actionType, err, existingDeviceTemplate) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备模板更新成功,但响应生成失败", actionType, "响应序列化失败", existingDeviceTemplate) diff --git a/internal/app/controller/management/pig_farm_controller.go b/internal/app/controller/management/pig_farm_controller.go index e08c08c..1dcc7b1 100644 --- a/internal/app/controller/management/pig_farm_controller.go +++ b/internal/app/controller/management/pig_farm_controller.go @@ -5,60 +5,13 @@ import ( "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) -// --- 数据传输对象 (DTOs) --- - -// PigHouseResponse 定义了猪舍信息的响应结构 -type PigHouseResponse struct { - ID uint `json:"id"` - Name string `json:"name"` - Description string `json:"description"` -} - -// PenResponse 定义了猪栏信息的响应结构 -type PenResponse struct { - ID uint `json:"id"` - PenNumber string `json:"pen_number"` - HouseID uint `json:"house_id"` - Capacity int `json:"capacity"` - Status models.PenStatus `json:"status"` - PigBatchID uint `json:"pig_batch_id"` -} - -// CreatePigHouseRequest 定义了创建猪舍的请求结构 -type CreatePigHouseRequest struct { - Name string `json:"name" binding:"required"` - Description string `json:"description"` -} - -// UpdatePigHouseRequest 定义了更新猪舍的请求结构 -type UpdatePigHouseRequest struct { - Name string `json:"name" binding:"required"` - Description string `json:"description"` -} - -// CreatePenRequest 定义了创建猪栏的请求结构 -type CreatePenRequest struct { - PenNumber string `json:"pen_number" binding:"required"` - HouseID uint `json:"house_id" binding:"required"` - Capacity int `json:"capacity" binding:"required"` - Status models.PenStatus `json:"status" binding:"required"` -} - -// UpdatePenRequest 定义了更新猪栏的请求结构 -type UpdatePenRequest struct { - PenNumber string `json:"pen_number" binding:"required"` - HouseID uint `json:"house_id" binding:"required"` - Capacity int `json:"capacity" binding:"required"` - Status models.PenStatus `json:"status" binding:"required"` -} - // --- 控制器定义 --- // PigFarmController 负责处理猪舍和猪栏相关的API请求 @@ -83,12 +36,12 @@ func NewPigFarmController(logger *logs.Logger, service service.PigFarmService) * // @Tags 猪场管理 // @Accept json // @Produce json -// @Param body body CreatePigHouseRequest true "猪舍信息" -// @Success 201 {object} controller.Response{data=PigHouseResponse} "创建成功" +// @Param body body dto.CreatePigHouseRequest true "猪舍信息" +// @Success 201 {object} controller.Response{data=dto.PigHouseResponse} "创建成功" // @Router /api/v1/pighouses [post] func (c *PigFarmController) CreatePigHouse(ctx *gin.Context) { const action = "创建猪舍" - var req CreatePigHouseRequest + var req dto.CreatePigHouseRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) return @@ -101,7 +54,7 @@ func (c *PigFarmController) CreatePigHouse(ctx *gin.Context) { return } - resp := PigHouseResponse{ + resp := dto.PigHouseResponse{ ID: house.ID, Name: house.Name, Description: house.Description, @@ -115,7 +68,7 @@ func (c *PigFarmController) CreatePigHouse(ctx *gin.Context) { // @Tags 猪场管理 // @Produce json // @Param id path int true "猪舍ID" -// @Success 200 {object} controller.Response{data=PigHouseResponse} "获取成功" +// @Success 200 {object} controller.Response{data=dto.PigHouseResponse} "获取成功" // @Router /api/v1/pighouses/{id} [get] func (c *PigFarmController) GetPigHouse(ctx *gin.Context) { const action = "获取猪舍" @@ -136,7 +89,7 @@ func (c *PigFarmController) GetPigHouse(ctx *gin.Context) { return } - resp := PigHouseResponse{ + resp := dto.PigHouseResponse{ ID: house.ID, Name: house.Name, Description: house.Description, @@ -149,7 +102,7 @@ func (c *PigFarmController) GetPigHouse(ctx *gin.Context) { // @Description 获取所有猪舍的列表 // @Tags 猪场管理 // @Produce json -// @Success 200 {object} controller.Response{data=[]PigHouseResponse} "获取成功" +// @Success 200 {object} controller.Response{data=[]dto.PigHouseResponse} "获取成功" // @Router /api/v1/pighouses [get] func (c *PigFarmController) ListPigHouses(ctx *gin.Context) { const action = "获取猪舍列表" @@ -160,9 +113,9 @@ func (c *PigFarmController) ListPigHouses(ctx *gin.Context) { return } - var resp []PigHouseResponse + var resp []dto.PigHouseResponse for _, house := range houses { - resp = append(resp, PigHouseResponse{ + resp = append(resp, dto.PigHouseResponse{ ID: house.ID, Name: house.Name, Description: house.Description, @@ -179,8 +132,8 @@ func (c *PigFarmController) ListPigHouses(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path int true "猪舍ID" -// @Param body body UpdatePigHouseRequest true "猪舍信息" -// @Success 200 {object} controller.Response{data=PigHouseResponse} "更新成功" +// @Param body body dto.UpdatePigHouseRequest true "猪舍信息" +// @Success 200 {object} controller.Response{data=dto.PigHouseResponse} "更新成功" // @Router /api/v1/pighouses/{id} [put] func (c *PigFarmController) UpdatePigHouse(ctx *gin.Context) { const action = "更新猪舍" @@ -190,7 +143,7 @@ func (c *PigFarmController) UpdatePigHouse(ctx *gin.Context) { return } - var req UpdatePigHouseRequest + var req dto.UpdatePigHouseRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) return @@ -207,7 +160,7 @@ func (c *PigFarmController) UpdatePigHouse(ctx *gin.Context) { return } - resp := PigHouseResponse{ + resp := dto.PigHouseResponse{ ID: house.ID, Name: house.Name, Description: house.Description, @@ -252,12 +205,12 @@ func (c *PigFarmController) DeletePigHouse(ctx *gin.Context) { // @Tags 猪场管理 // @Accept json // @Produce json -// @Param body body CreatePenRequest true "猪栏信息" -// @Success 201 {object} controller.Response{data=PenResponse} "创建成功" +// @Param body body dto.CreatePenRequest true "猪栏信息" +// @Success 201 {object} controller.Response{data=dto.PenResponse} "创建成功" // @Router /api/v1/pens [post] func (c *PigFarmController) CreatePen(ctx *gin.Context) { const action = "创建猪栏" - var req CreatePenRequest + var req dto.CreatePenRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) return @@ -270,7 +223,7 @@ func (c *PigFarmController) CreatePen(ctx *gin.Context) { return } - resp := PenResponse{ + resp := dto.PenResponse{ ID: pen.ID, PenNumber: pen.PenNumber, HouseID: pen.HouseID, @@ -287,7 +240,7 @@ func (c *PigFarmController) CreatePen(ctx *gin.Context) { // @Tags 猪场管理 // @Produce json // @Param id path int true "猪栏ID" -// @Success 200 {object} controller.Response{data=PenResponse} "获取成功" +// @Success 200 {object} controller.Response{data=dto.PenResponse} "获取成功" // @Router /api/v1/pens/{id} [get] func (c *PigFarmController) GetPen(ctx *gin.Context) { const action = "获取猪栏" @@ -308,7 +261,7 @@ func (c *PigFarmController) GetPen(ctx *gin.Context) { return } - resp := PenResponse{ + resp := dto.PenResponse{ ID: pen.ID, PenNumber: pen.PenNumber, HouseID: pen.HouseID, @@ -324,7 +277,7 @@ func (c *PigFarmController) GetPen(ctx *gin.Context) { // @Description 获取所有猪栏的列表 // @Tags 猪场管理 // @Produce json -// @Success 200 {object} controller.Response{data=[]PenResponse} "获取成功" +// @Success 200 {object} controller.Response{data=[]dto.PenResponse} "获取成功" // @Router /api/v1/pens [get] func (c *PigFarmController) ListPens(ctx *gin.Context) { const action = "获取猪栏列表" @@ -335,9 +288,9 @@ func (c *PigFarmController) ListPens(ctx *gin.Context) { return } - var resp []PenResponse + var resp []dto.PenResponse for _, pen := range pens { - resp = append(resp, PenResponse{ + resp = append(resp, dto.PenResponse{ ID: pen.ID, PenNumber: pen.PenNumber, HouseID: pen.HouseID, @@ -357,8 +310,8 @@ func (c *PigFarmController) ListPens(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path int true "猪栏ID" -// @Param body body UpdatePenRequest true "猪栏信息" -// @Success 200 {object} controller.Response{data=PenResponse} "更新成功" +// @Param body body dto.UpdatePenRequest true "猪栏信息" +// @Success 200 {object} controller.Response{data=dto.PenResponse} "更新成功" // @Router /api/v1/pens/{id} [put] func (c *PigFarmController) UpdatePen(ctx *gin.Context) { const action = "更新猪栏" @@ -368,7 +321,7 @@ func (c *PigFarmController) UpdatePen(ctx *gin.Context) { return } - var req UpdatePenRequest + var req dto.UpdatePenRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) return @@ -385,7 +338,7 @@ func (c *PigFarmController) UpdatePen(ctx *gin.Context) { return } - resp := PenResponse{ + resp := dto.PenResponse{ ID: pen.ID, PenNumber: pen.PenNumber, HouseID: pen.HouseID, diff --git a/internal/app/controller/plan/plan_controller.go b/internal/app/controller/plan/plan_controller.go index 284df63..e7a8862 100644 --- a/internal/app/controller/plan/plan_controller.go +++ b/internal/app/controller/plan/plan_controller.go @@ -5,6 +5,7 @@ import ( "strconv" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/task" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" @@ -13,80 +14,6 @@ import ( "gorm.io/gorm" ) -// --- 请求和响应 DTO 定义 --- - -// CreatePlanRequest 定义创建计划请求的结构体 -type CreatePlanRequest struct { - Name string `json:"name" binding:"required" example:"猪舍温度控制计划"` - Description string `json:"description" example:"根据温度自动调节风扇和加热器"` - ExecutionType models.PlanExecutionType `json:"execution_type" binding:"required" example:"自动"` - ExecuteNum uint `json:"execute_num,omitempty" example:"10"` - CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` - SubPlanIDs []uint `json:"sub_plan_ids,omitempty"` - Tasks []TaskRequest `json:"tasks,omitempty"` -} - -// PlanResponse 定义计划详情响应的结构体 -type PlanResponse struct { - ID uint `json:"id" example:"1"` - Name string `json:"name" example:"猪舍温度控制计划"` - Description string `json:"description" example:"根据温度自动调节风扇和加热器"` - ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"` - Status models.PlanStatus `json:"status" example:"已启用"` - ExecuteNum uint `json:"execute_num" example:"10"` - ExecuteCount uint `json:"execute_count" example:"0"` - CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` - ContentType models.PlanContentType `json:"content_type" example:"任务"` - SubPlans []SubPlanResponse `json:"sub_plans,omitempty"` - Tasks []TaskResponse `json:"tasks,omitempty"` -} - -// ListPlansResponse 定义获取计划列表响应的结构体 -type ListPlansResponse struct { - Plans []PlanResponse `json:"plans"` - Total int `json:"total" example:"100"` -} - -// UpdatePlanRequest 定义更新计划请求的结构体 -type UpdatePlanRequest struct { - Name string `json:"name" example:"猪舍温度控制计划V2"` - Description string `json:"description" example:"更新后的描述"` - ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"` - ExecuteNum uint `json:"execute_num,omitempty" example:"10"` - CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` - SubPlanIDs []uint `json:"sub_plan_ids,omitempty"` - Tasks []TaskRequest `json:"tasks,omitempty"` -} - -// SubPlanResponse 定义子计划响应结构体 -type SubPlanResponse struct { - ID uint `json:"id" example:"1"` - ParentPlanID uint `json:"parent_plan_id" example:"1"` - ChildPlanID uint `json:"child_plan_id" example:"2"` - ExecutionOrder int `json:"execution_order" example:"1"` - ChildPlan *PlanResponse `json:"child_plan,omitempty"` -} - -// TaskRequest 定义任务请求结构体 -type TaskRequest struct { - Name string `json:"name" example:"打开风扇"` - Description string `json:"description" example:"打开1号风扇"` - ExecutionOrder int `json:"execution_order" example:"1"` - Type models.TaskType `json:"type" example:"等待"` - Parameters map[string]interface{} `json:"parameters,omitempty"` -} - -// TaskResponse 定义任务响应结构体 -type TaskResponse struct { - ID int `json:"id" example:"1"` - PlanID uint `json:"plan_id" example:"1"` - Name string `json:"name" example:"打开风扇"` - Description string `json:"description" example:"打开1号风扇"` - ExecutionOrder int `json:"execution_order" example:"1"` - Type models.TaskType `json:"type" example:"等待"` - Parameters map[string]interface{} `json:"parameters,omitempty"` -} - // --- Controller 定义 --- // Controller 定义了计划相关的控制器 @@ -113,11 +40,11 @@ func NewController(logger *logs.Logger, planRepo repository.PlanRepository, anal // @Tags 计划管理 // @Accept json // @Produce json -// @Param plan body CreatePlanRequest true "计划信息" -// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为201代表创建成功" +// @Param plan body dto.CreatePlanRequest true "计划信息" +// @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为201代表创建成功" // @Router /api/v1/plans [post] func (c *Controller) CreatePlan(ctx *gin.Context) { - var req CreatePlanRequest + var req dto.CreatePlanRequest const actionType = "创建计划" if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) @@ -126,7 +53,7 @@ func (c *Controller) CreatePlan(ctx *gin.Context) { } // 使用已有的转换函数,它已经包含了验证和重排逻辑 - planToCreate, err := PlanFromCreateRequest(&req) + planToCreate, err := dto.NewPlanFromCreateRequest(&req) if err != nil { c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划数据校验失败: "+err.Error(), actionType, "计划数据校验失败", req) @@ -155,7 +82,7 @@ func (c *Controller) CreatePlan(ctx *gin.Context) { } // 使用已有的转换函数将创建后的模型转换为响应对象 - resp, err := PlanToResponse(planToCreate) + resp, err := dto.NewPlanToResponse(planToCreate) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "计划创建成功,但响应生成失败", actionType, "响应序列化失败", planToCreate) @@ -173,7 +100,7 @@ func (c *Controller) CreatePlan(ctx *gin.Context) { // @Tags 计划管理 // @Produce json // @Param id path int true "计划ID" -// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表成功获取" +// @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为200代表成功获取" // @Router /api/v1/plans/{id} [get] func (c *Controller) GetPlan(ctx *gin.Context) { const actionType = "获取计划详情" @@ -202,7 +129,7 @@ func (c *Controller) GetPlan(ctx *gin.Context) { } // 3. 将模型转换为响应 DTO - resp, err := PlanToResponse(plan) + resp, err := dto.NewPlanToResponse(plan) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, plan) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划详情失败: 内部数据格式错误", actionType, "响应序列化失败", plan) @@ -219,7 +146,7 @@ func (c *Controller) GetPlan(ctx *gin.Context) { // @Description 获取所有计划的列表 // @Tags 计划管理 // @Produce json -// @Success 200 {object} controller.Response{data=plan.ListPlansResponse} "业务码为200代表成功获取列表" +// @Success 200 {object} controller.Response{data=[]dto.PlanResponse} "业务码为200代表成功获取列表" // @Router /api/v1/plans [get] func (c *Controller) ListPlans(ctx *gin.Context) { const actionType = "获取计划列表" @@ -232,9 +159,9 @@ func (c *Controller) ListPlans(ctx *gin.Context) { } // 2. 将模型转换为响应 DTO - planResponses := make([]PlanResponse, 0, len(plans)) + planResponses := make([]dto.PlanResponse, 0, len(plans)) for _, p := range plans { - resp, err := PlanToResponse(&p) + resp, err := dto.NewPlanToResponse(&p) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, p) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表失败: 内部数据格式错误", actionType, "响应序列化失败", p) @@ -244,7 +171,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) { } // 3. 构造并发送成功响应 - resp := ListPlansResponse{ + resp := dto.ListPlansResponse{ Plans: planResponses, Total: len(planResponses), } @@ -259,8 +186,8 @@ func (c *Controller) ListPlans(ctx *gin.Context) { // @Accept json // @Produce json // @Param id path int true "计划ID" -// @Param plan body UpdatePlanRequest true "更新后的计划信息" -// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表更新成功" +// @Param plan body dto.UpdatePlanRequest true "更新后的计划信息" +// @Success 200 {object} controller.Response{data=dto.PlanResponse} "业务码为200代表更新成功" // @Router /api/v1/plans/{id} [put] func (c *Controller) UpdatePlan(ctx *gin.Context) { const actionType = "更新计划" @@ -274,7 +201,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { } // 2. 绑定请求体 - var req UpdatePlanRequest + var req dto.UpdatePlanRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) @@ -282,7 +209,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { } // 3. 将请求转换为模型(转换函数带校验) - planToUpdate, err := PlanFromUpdateRequest(&req) + planToUpdate, err := dto.NewPlanFromUpdateRequest(&req) if err != nil { c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划数据校验失败: "+err.Error(), actionType, "计划数据校验失败", req) @@ -306,8 +233,8 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id) return } - c.logger.Errorf("%s: 获取计划详情失败: %v, ID: %d", actionType, err, id) - controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划详情时发生内部错误", actionType, "数据库查询失败", id) + c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id) + controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id) return } @@ -337,7 +264,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { } // 7. 将模型转换为响应 DTO - resp, err := PlanToResponse(updatedPlan) + resp, err := dto.NewPlanToResponse(updatedPlan) if err != nil { c.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "计划更新成功,但响应生成失败", actionType, "响应序列化失败", updatedPlan) diff --git a/internal/app/controller/user/user_controller.go b/internal/app/controller/user/user_controller.go index 7d6d1e8..27689d4 100644 --- a/internal/app/controller/user/user_controller.go +++ b/internal/app/controller/user/user_controller.go @@ -5,6 +5,7 @@ import ( "time" "git.huangwc.com/pig/pig-farm-controller/internal/app/controller" + "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/token" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" @@ -31,50 +32,6 @@ func NewController(userRepo repository.UserRepository, auditRepo repository.User } } -// --- DTOs --- - -// CreateUserRequest 定义创建用户请求的结构体 -type CreateUserRequest struct { - Username string `json:"username" binding:"required" example:"newuser"` - Password string `json:"password" binding:"required" example:"password123"` -} - -// LoginRequest 定义登录请求的结构体 -type LoginRequest struct { - // Identifier 可以是用户名、邮箱、手机号、微信号或飞书账号 - Identifier string `json:"identifier" binding:"required" example:"testuser"` - Password string `json:"password" binding:"required" example:"password123"` -} - -// CreateUserResponse 定义创建用户成功响应的结构体 -type CreateUserResponse struct { - Username string `json:"username" example:"newuser"` - ID uint `json:"id" example:"1"` -} - -// LoginResponse 定义登录成功响应的结构体 -type LoginResponse struct { - Username string `json:"username" example:"testuser"` - ID uint `json:"id" example:"1"` - Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."` -} - -// HistoryResponse 定义单条操作历史的响应结构体 -type HistoryResponse struct { - UserID uint `json:"user_id" example:"101"` - Username string `json:"username" example:"testuser"` - ActionType string `json:"action_type" example:"更新设备"` - Description string `json:"description" example:"设备更新成功"` - TargetResource interface{} `json:"target_resource"` - Time string `json:"time"` -} - -// ListHistoryResponse 定义操作历史列表的响应结构体 -type ListHistoryResponse struct { - History []HistoryResponse `json:"history"` - Total int64 `json:"total" example:"100"` -} - // --- Controller Methods --- // CreateUser godoc @@ -83,11 +40,11 @@ type ListHistoryResponse struct { // @Tags 用户管理 // @Accept json // @Produce json -// @Param user body CreateUserRequest true "用户信息" -// @Success 200 {object} controller.Response{data=user.CreateUserResponse} "业务码为201代表创建成功" +// @Param user body dto.CreateUserRequest true "用户信息" +// @Success 200 {object} controller.Response{data=dto.CreateUserResponse} "业务码为201代表创建成功" // @Router /api/v1/users [post] func (c *Controller) CreateUser(ctx *gin.Context) { - var req CreateUserRequest + var req dto.CreateUserRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("创建用户: 参数绑定失败: %v", err) controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error()) @@ -114,7 +71,7 @@ func (c *Controller) CreateUser(ctx *gin.Context) { return } - controller.SendResponse(ctx, controller.CodeCreated, "用户创建成功", CreateUserResponse{ + controller.SendResponse(ctx, controller.CodeCreated, "用户创建成功", dto.CreateUserResponse{ Username: user.Username, ID: user.ID, }) @@ -126,11 +83,11 @@ func (c *Controller) CreateUser(ctx *gin.Context) { // @Tags 用户管理 // @Accept json // @Produce json -// @Param credentials body LoginRequest true "登录凭证" -// @Success 200 {object} controller.Response{data=user.LoginResponse} "业务码为200代表登录成功" +// @Param credentials body dto.LoginRequest true "登录凭证" +// @Success 200 {object} controller.Response{data=dto.LoginResponse} "业务码为200代表登录成功" // @Router /api/v1/users/login [post] func (c *Controller) Login(ctx *gin.Context) { - var req LoginRequest + var req dto.LoginRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("登录: 参数绑定失败: %v", err) controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error()) @@ -162,7 +119,7 @@ func (c *Controller) Login(ctx *gin.Context) { return } - controller.SendResponse(ctx, controller.CodeSuccess, "登录成功", LoginResponse{ + controller.SendResponse(ctx, controller.CodeSuccess, "登录成功", dto.LoginResponse{ Username: user.Username, ID: user.ID, Token: tokenString, @@ -178,7 +135,7 @@ func (c *Controller) Login(ctx *gin.Context) { // @Param page query int false "页码" default(1) // @Param page_size query int false "每页大小" default(10) // @Param action_type query string false "按操作类型过滤" -// @Success 200 {object} controller.Response{data=user.ListHistoryResponse} "业务码为200代表成功获取" +// @Success 200 {object} controller.Response{data=dto.ListHistoryResponse} "业务码为200代表成功获取" // @Router /api/v1/users/{id}/history [get] func (c *Controller) ListUserHistory(ctx *gin.Context) { const actionType = "获取用户操作历史" @@ -221,9 +178,9 @@ func (c *Controller) ListUserHistory(ctx *gin.Context) { } // 4. 将数据库模型转换为响应 DTO - historyResponses := make([]HistoryResponse, 0, len(logs)) + historyResponses := make([]dto.HistoryResponse, 0, len(logs)) for _, log := range logs { - historyResponses = append(historyResponses, HistoryResponse{ + historyResponses = append(historyResponses, dto.HistoryResponse{ UserID: log.UserID, Username: log.Username, ActionType: log.ActionType, @@ -234,7 +191,7 @@ func (c *Controller) ListUserHistory(ctx *gin.Context) { } // 5. 发送成功响应 - resp := ListHistoryResponse{ + resp := dto.ListHistoryResponse{ History: historyResponses, Total: total, } diff --git a/internal/app/dto/device_converter.go b/internal/app/dto/device_converter.go new file mode 100644 index 0000000..20a5ce3 --- /dev/null +++ b/internal/app/dto/device_converter.go @@ -0,0 +1,142 @@ +package dto + +import ( + "encoding/json" + "fmt" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + +// 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 := device.ParseProperties(&props); err != nil { + return nil, fmt.Errorf("解析设备属性失败 (ID: %d): %w", device.ID, err) + } + } + + // 确保 DeviceTemplate 和 AreaController 已预加载 + deviceTemplateName := "" + if device.DeviceTemplate.ID != 0 { + deviceTemplateName = device.DeviceTemplate.Name + } + + areaControllerName := "" + if device.AreaController.ID != 0 { + areaControllerName = device.AreaController.Name + } + + return &DeviceResponse{ + ID: device.ID, + Name: device.Name, + DeviceTemplateID: device.DeviceTemplateID, + DeviceTemplateName: deviceTemplateName, + AreaControllerID: device.AreaControllerID, + AreaControllerName: areaControllerName, + 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 +} + +// NewAreaControllerResponse 从数据库模型创建一个新的区域主控响应 DTO +func NewAreaControllerResponse(ac *models.AreaController) (*AreaControllerResponse, error) { + if ac == nil { + return nil, nil + } + + var props map[string]interface{} + if len(ac.Properties) > 0 && string(ac.Properties) != "null" { + if err := json.Unmarshal(ac.Properties, &props); err != nil { + return nil, fmt.Errorf("解析区域主控属性失败 (ID: %d): %w", ac.ID, err) + } + } + + return &AreaControllerResponse{ + ID: ac.ID, + Name: ac.Name, + NetworkID: ac.NetworkID, + Location: ac.Location, + Status: ac.Status, + Properties: props, + CreatedAt: ac.CreatedAt.Format(time.RFC3339), + UpdatedAt: ac.UpdatedAt.Format(time.RFC3339), + }, nil +} + +// NewListAreaControllerResponse 从数据库模型切片创建一个新的区域主控列表响应 DTO 切片 +func NewListAreaControllerResponse(acs []*models.AreaController) ([]*AreaControllerResponse, error) { + list := make([]*AreaControllerResponse, 0, len(acs)) + for _, ac := range acs { + resp, err := NewAreaControllerResponse(ac) + if err != nil { + return nil, err + } + list = append(list, resp) + } + return list, nil +} + +// NewDeviceTemplateResponse 从数据库模型创建一个新的设备模板响应 DTO +func NewDeviceTemplateResponse(dt *models.DeviceTemplate) (*DeviceTemplateResponse, error) { + if dt == nil { + return nil, nil + } + + var commands map[string]interface{} + if err := dt.ParseCommands(&commands); err != nil { + return nil, fmt.Errorf("解析设备模板命令失败 (ID: %d): %w", dt.ID, err) + } + + var values []models.ValueDescriptor + if dt.Category == models.CategorySensor { + if err := dt.ParseValues(&values); err != nil { + return nil, fmt.Errorf("解析设备模板值描述符失败 (ID: %d): %w", dt.ID, err) + } + } + + return &DeviceTemplateResponse{ + ID: dt.ID, + Name: dt.Name, + Manufacturer: dt.Manufacturer, + Description: dt.Description, + Category: dt.Category, + Commands: commands, + Values: values, + CreatedAt: dt.CreatedAt.Format(time.RFC3339), + UpdatedAt: dt.UpdatedAt.Format(time.RFC3339), + }, nil +} + +// NewListDeviceTemplateResponse 从数据库模型切片创建一个新的设备模板列表响应 DTO 切片 +func NewListDeviceTemplateResponse(dts []*models.DeviceTemplate) ([]*DeviceTemplateResponse, error) { + list := make([]*DeviceTemplateResponse, 0, len(dts)) + for _, dt := range dts { + resp, err := NewDeviceTemplateResponse(dt) + if err != nil { + return nil, err + } + list = append(list, resp) + } + return list, nil +} diff --git a/internal/app/dto/device_dto.go b/internal/app/dto/device_dto.go new file mode 100644 index 0000000..d37f3f6 --- /dev/null +++ b/internal/app/dto/device_dto.go @@ -0,0 +1,96 @@ +package dto + +import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + +// CreateDeviceRequest 定义了创建设备时需要传入的参数 +type CreateDeviceRequest struct { + Name string `json:"name" binding:"required"` + DeviceTemplateID uint `json:"device_template_id" binding:"required"` + AreaControllerID uint `json:"area_controller_id" binding:"required"` + Location string `json:"location,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` +} + +// UpdateDeviceRequest 定义了更新设备时需要传入的参数 +type UpdateDeviceRequest struct { + Name string `json:"name" binding:"required"` + DeviceTemplateID uint `json:"device_template_id" binding:"required"` + AreaControllerID uint `json:"area_controller_id" binding:"required"` + Location string `json:"location,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` +} + +// CreateAreaControllerRequest 定义了创建区域主控时需要传入的参数 +type CreateAreaControllerRequest struct { + Name string `json:"name" binding:"required"` + NetworkID string `json:"network_id" binding:"required"` + Location string `json:"location,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` +} + +// UpdateAreaControllerRequest 定义了更新区域主控时需要传入的参数 +type UpdateAreaControllerRequest struct { + Name string `json:"name" binding:"required"` + NetworkID string `json:"network_id" binding:"required"` + Location string `json:"location,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` +} + +// CreateDeviceTemplateRequest 定义了创建设备模板时需要传入的参数 +type CreateDeviceTemplateRequest struct { + Name string `json:"name" binding:"required"` + Manufacturer string `json:"manufacturer,omitempty"` + Description string `json:"description,omitempty"` + Category models.DeviceCategory `json:"category" binding:"required"` + Commands map[string]interface{} `json:"commands" binding:"required"` + Values []models.ValueDescriptor `json:"values,omitempty"` +} + +// UpdateDeviceTemplateRequest 定义了更新设备模板时需要传入的参数 +type UpdateDeviceTemplateRequest struct { + Name string `json:"name" binding:"required"` + Manufacturer string `json:"manufacturer,omitempty"` + Description string `json:"description,omitempty"` + Category models.DeviceCategory `json:"category" binding:"required"` + Commands map[string]interface{} `json:"commands" binding:"required"` + Values []models.ValueDescriptor `json:"values,omitempty"` +} + +// DeviceResponse 定义了返回给客户端的单个设备信息的结构 +type DeviceResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + DeviceTemplateID uint `json:"device_template_id"` + DeviceTemplateName string `json:"device_template_name"` + AreaControllerID uint `json:"area_controller_id"` + AreaControllerName string `json:"area_controller_name"` + Location string `json:"location"` + Properties map[string]interface{} `json:"properties"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构 +type AreaControllerResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + NetworkID string `json:"network_id"` + Location string `json:"location"` + Status string `json:"status"` + Properties map[string]interface{} `json:"properties"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// DeviceTemplateResponse 定义了返回给客户端的单个设备模板信息的结构 +type DeviceTemplateResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Manufacturer string `json:"manufacturer"` + Description string `json:"description"` + Category models.DeviceCategory `json:"category"` + Commands map[string]interface{} `json:"commands"` + Values []models.ValueDescriptor `json:"values"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/internal/app/dto/pig_farm_dto.go b/internal/app/dto/pig_farm_dto.go new file mode 100644 index 0000000..2b5b29b --- /dev/null +++ b/internal/app/dto/pig_farm_dto.go @@ -0,0 +1,48 @@ +package dto + +import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + +// PigHouseResponse 定义了猪舍信息的响应结构 +type PigHouseResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} + +// PenResponse 定义了猪栏信息的响应结构 +type PenResponse struct { + ID uint `json:"id"` + PenNumber string `json:"pen_number"` + HouseID uint `json:"house_id"` + Capacity int `json:"capacity"` + Status models.PenStatus `json:"status"` + PigBatchID uint `json:"pig_batch_id"` +} + +// CreatePigHouseRequest 定义了创建猪舍的请求结构 +type CreatePigHouseRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` +} + +// UpdatePigHouseRequest 定义了更新猪舍的请求结构 +type UpdatePigHouseRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` +} + +// CreatePenRequest 定义了创建猪栏的请求结构 +type CreatePenRequest struct { + PenNumber string `json:"pen_number" binding:"required"` + HouseID uint `json:"house_id" binding:"required"` + Capacity int `json:"capacity" binding:"required"` + Status models.PenStatus `json:"status" binding:"required"` +} + +// UpdatePenRequest 定义了更新猪栏的请求结构 +type UpdatePenRequest struct { + PenNumber string `json:"pen_number" binding:"required"` + HouseID uint `json:"house_id" binding:"required"` + Capacity int `json:"capacity" binding:"required"` + Status models.PenStatus `json:"status" binding:"required"` +} diff --git a/internal/app/dto/plan_converter.go b/internal/app/dto/plan_converter.go new file mode 100644 index 0000000..ed0d5fd --- /dev/null +++ b/internal/app/dto/plan_converter.go @@ -0,0 +1,145 @@ +package dto + +import ( + "encoding/json" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" +) + +// NewPlanFromCreateRequest 将 CreatePlanRequest 转换为 models.Plan +func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { + plan := &models.Plan{ + Name: req.Name, + Description: req.Description, + ExecutionType: req.ExecutionType, + ExecuteNum: req.ExecuteNum, + CronExpression: req.CronExpression, + Status: models.PlanStatusDisabled, // 默认创建时为禁用状态 + } + + if len(req.SubPlanIDs) > 0 { + plan.ContentType = models.PlanContentTypeSubPlans + for _, subPlanID := range req.SubPlanIDs { + plan.SubPlans = append(plan.SubPlans, models.SubPlan{ + ChildPlanID: subPlanID, + }) + } + } else if len(req.Tasks) > 0 { + plan.ContentType = models.PlanContentTypeTasks + for _, taskReq := range req.Tasks { + parametersJSON, err := json.Marshal(taskReq.Parameters) + if err != nil { + return nil, fmt.Errorf("序列化任务参数失败: %w", err) + } + plan.Tasks = append(plan.Tasks, models.Task{ + Name: taskReq.Name, + Description: taskReq.Description, + ExecutionOrder: taskReq.ExecutionOrder, + Type: taskReq.Type, + Parameters: parametersJSON, + }) + } + } else { + return nil, errors.New("计划必须包含子计划或任务") + } + + return plan, nil +} + +// NewPlanFromUpdateRequest 将 UpdatePlanRequest 转换为 models.Plan +func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { + plan := &models.Plan{ + Name: req.Name, + Description: req.Description, + ExecutionType: req.ExecutionType, + ExecuteNum: req.ExecuteNum, + CronExpression: req.CronExpression, + } + + if len(req.SubPlanIDs) > 0 { + plan.ContentType = models.PlanContentTypeSubPlans + for _, subPlanID := range req.SubPlanIDs { + plan.SubPlans = append(plan.SubPlans, models.SubPlan{ + ChildPlanID: subPlanID, + }) + } + } else if len(req.Tasks) > 0 { + plan.ContentType = models.PlanContentTypeTasks + for _, taskReq := range req.Tasks { + parametersJSON, err := json.Marshal(taskReq.Parameters) + if err != nil { + return nil, fmt.Errorf("序列化任务参数失败: %w", err) + } + plan.Tasks = append(plan.Tasks, models.Task{ + Name: taskReq.Name, + Description: taskReq.Description, + ExecutionOrder: taskReq.ExecutionOrder, + Type: taskReq.Type, + Parameters: parametersJSON, + }) + } + } else { + return nil, errors.New("计划必须包含子计划或任务") + } + + return plan, nil +} + +// NewPlanToResponse 将 models.Plan 转换为 PlanResponse +func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) { + if plan == nil { + return nil, nil + } + + resp := &PlanResponse{ + ID: plan.ID, + Name: plan.Name, + Description: plan.Description, + ExecutionType: plan.ExecutionType, + Status: plan.Status, + ExecuteNum: plan.ExecuteNum, + ExecuteCount: plan.ExecuteCount, + CronExpression: plan.CronExpression, + ContentType: plan.ContentType, + } + + if plan.ContentType == models.PlanContentTypeSubPlans && len(plan.SubPlans) > 0 { + resp.SubPlans = make([]SubPlanResponse, 0, len(plan.SubPlans)) + for _, sp := range plan.SubPlans { + childPlanResp, err := NewPlanToResponse(sp.ChildPlan) // 递归调用 + if err != nil { + return nil, err + } + resp.SubPlans = append(resp.SubPlans, SubPlanResponse{ + ID: sp.ID, + ParentPlanID: sp.ParentPlanID, + ChildPlanID: sp.ChildPlanID, + ExecutionOrder: sp.ExecutionOrder, + ChildPlan: childPlanResp, + }) + } + } else if plan.ContentType == models.PlanContentTypeTasks && len(plan.Tasks) > 0 { + resp.Tasks = make([]TaskResponse, 0, len(plan.Tasks)) + for _, task := range plan.Tasks { + var parameters map[string]interface{} + if len(task.Parameters) > 0 && string(task.Parameters) != "null" { + if err := json.Unmarshal(task.Parameters, ¶meters); err != nil { + return nil, fmt.Errorf("解析任务参数失败 (ID: %d): %w", task.ID, err) + } + } + resp.Tasks = append(resp.Tasks, TaskResponse{ + ID: task.ID, + PlanID: task.PlanID, + Name: task.Name, + Description: task.Description, + ExecutionOrder: task.ExecutionOrder, + Type: task.Type, + Parameters: parameters, + }) + } + } + + return resp, nil +} diff --git a/internal/app/dto/plan_dto.go b/internal/app/dto/plan_dto.go new file mode 100644 index 0000000..37935a2 --- /dev/null +++ b/internal/app/dto/plan_dto.go @@ -0,0 +1,75 @@ +package dto + +import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + +// CreatePlanRequest 定义创建计划请求的结构体 +type CreatePlanRequest struct { + Name string `json:"name" binding:"required" example:"猪舍温度控制计划"` + Description string `json:"description" example:"根据温度自动调节风扇和加热器"` + ExecutionType models.PlanExecutionType `json:"execution_type" binding:"required" example:"自动"` + ExecuteNum uint `json:"execute_num,omitempty" example:"10"` + CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` + SubPlanIDs []uint `json:"sub_plan_ids,omitempty"` + Tasks []TaskRequest `json:"tasks,omitempty"` +} + +// PlanResponse 定义计划详情响应的结构体 +type PlanResponse struct { + ID uint `json:"id" example:"1"` + Name string `json:"name" example:"猪舍温度控制计划"` + Description string `json:"description" example:"根据温度自动调节风扇和加热器"` + ExecutionType models.PlanExecutionType `json:"execution_type" example:"自动"` + Status models.PlanStatus `json:"status" example:"已启用"` + ExecuteNum uint `json:"execute_num" example:"10"` + ExecuteCount uint `json:"execute_count" example:"0"` + CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` + ContentType models.PlanContentType `json:"content_type" example:"任务"` + SubPlans []SubPlanResponse `json:"sub_plans,omitempty"` + Tasks []TaskResponse `json:"tasks,omitempty"` +} + +// ListPlansResponse 定义获取计划列表响应的结构体 +type ListPlansResponse struct { + Plans []PlanResponse `json:"plans"` + Total int `json:"total" example:"100"` +} + +// UpdatePlanRequest 定义更新计划请求的结构体 +type UpdatePlanRequest struct { + Name string `json:"name" example:"猪舍温度控制计划V2"` + Description string `json:"description" example:"更新后的描述"` + ExecutionType models.PlanExecutionType `json:"execution_type" binding:"required" example:"自动"` + ExecuteNum uint `json:"execute_num,omitempty" example:"10"` + CronExpression string `json:"cron_expression" example:"0 0 6 * * *"` + SubPlanIDs []uint `json:"sub_plan_ids,omitempty"` + Tasks []TaskRequest `json:"tasks,omitempty"` +} + +// SubPlanResponse 定义子计划响应结构体 +type SubPlanResponse struct { + ID uint `json:"id" example:"1"` + ParentPlanID uint `json:"parent_plan_id" example:"1"` + ChildPlanID uint `json:"child_plan_id" example:"2"` + ExecutionOrder int `json:"execution_order" example:"1"` + ChildPlan *PlanResponse `json:"child_plan,omitempty"` +} + +// TaskRequest 定义任务请求结构体 +type TaskRequest struct { + Name string `json:"name" example:"打开风扇"` + Description string `json:"description" example:"打开1号风扇"` + ExecutionOrder int `json:"execution_order" example:"1"` + Type models.TaskType `json:"type" example:"等待"` + Parameters map[string]interface{} `json:"parameters,omitempty"` +} + +// TaskResponse 定义任务响应结构体 +type TaskResponse struct { + ID int `json:"id" example:"1"` + PlanID uint `json:"plan_id" example:"1"` + Name string `json:"name" example:"打开风扇"` + Description string `json:"description" example:"打开1号风扇"` + ExecutionOrder int `json:"execution_order" example:"1"` + Type models.TaskType `json:"type" example:"等待"` + Parameters map[string]interface{} `json:"parameters,omitempty"` +} diff --git a/internal/app/dto/user_dto.go b/internal/app/dto/user_dto.go new file mode 100644 index 0000000..20d5fe7 --- /dev/null +++ b/internal/app/dto/user_dto.go @@ -0,0 +1,43 @@ +package dto + +// CreateUserRequest 定义创建用户请求的结构体 +type CreateUserRequest struct { + Username string `json:"username" binding:"required" example:"newuser"` + Password string `json:"password" binding:"required" example:"password123"` +} + +// LoginRequest 定义登录请求的结构体 +type LoginRequest struct { + // Identifier 可以是用户名、邮箱、手机号、微信号或飞书账号 + Identifier string `json:"identifier" binding:"required" example:"testuser"` + Password string `json:"password" binding:"required" example:"password123"` +} + +// CreateUserResponse 定义创建用户成功响应的结构体 +type CreateUserResponse struct { + Username string `json:"username" example:"newuser"` + ID uint `json:"id" example:"1"` +} + +// LoginResponse 定义登录成功响应的结构体 +type LoginResponse struct { + Username string `json:"username" example:"testuser"` + ID uint `json:"id" example:"1"` + Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."` +} + +// HistoryResponse 定义单条操作历史的响应结构体 +type HistoryResponse struct { + UserID uint `json:"user_id" example:"101"` + Username string `json:"username" example:"testuser"` + ActionType string `json:"action_type" example:"更新设备"` + Description string `json:"description" example:"设备更新成功"` + TargetResource interface{} `json:"target_resource"` + Time string `json:"time"` +} + +// ListHistoryResponse 定义操作历史列表的响应结构体 +type ListHistoryResponse struct { + History []HistoryResponse `json:"history"` + Total int64 `json:"total" example:"100"` +}