ManualControl

This commit is contained in:
2025-10-13 10:39:51 +08:00
parent b4d31d3133
commit 9b6548c1b4
4 changed files with 87 additions and 12 deletions

View File

@@ -24,6 +24,7 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/app/service" "git.huangwc.com/pig/pig-farm-controller/internal/app/service"
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook" "git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit" "git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task" "git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token" "git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config" "git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
@@ -60,13 +61,14 @@ func NewAPI(cfg config.ServerConfig,
userRepo repository.UserRepository, userRepo repository.UserRepository,
deviceRepository repository.DeviceRepository, deviceRepository repository.DeviceRepository,
areaControllerRepository repository.AreaControllerRepository, areaControllerRepository repository.AreaControllerRepository,
deviceTemplateRepository repository.DeviceTemplateRepository, // 添加设备模板仓库 deviceTemplateRepository repository.DeviceTemplateRepository,
planRepository repository.PlanRepository, planRepository repository.PlanRepository,
pigFarmService service.PigFarmService, pigFarmService service.PigFarmService,
pigBatchService service.PigBatchService, // 添加猪群服务 pigBatchService service.PigBatchService,
userActionLogRepository repository.UserActionLogRepository, userActionLogRepository repository.UserActionLogRepository,
tokenService token.TokenService, tokenService token.TokenService,
auditService audit.Service, // 注入审计服务 auditService audit.Service,
deviceService domain_device.Service,
listenHandler webhook.ListenHandler, listenHandler webhook.ListenHandler,
analysisTaskManager *task.AnalysisPlanTaskManager) *API { analysisTaskManager *task.AnalysisPlanTaskManager) *API {
// 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式) // 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式)
@@ -93,7 +95,7 @@ func NewAPI(cfg config.ServerConfig,
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService), userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService),
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, logger), deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, deviceService, logger),
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
planController: plan.NewController(logger, planRepository, analysisTaskManager), planController: plan.NewController(logger, planRepository, analysisTaskManager),
// 在 NewAPI 中初始化猪场管理控制器 // 在 NewAPI 中初始化猪场管理控制器
@@ -160,11 +162,12 @@ func (a *API) setupRoutes() {
// 设备相关路由组 // 设备相关路由组
deviceGroup := authGroup.Group("/devices") deviceGroup := authGroup.Group("/devices")
{ {
deviceGroup.POST("", a.deviceController.CreateDevice) // 创建设备 deviceGroup.POST("", a.deviceController.CreateDevice) // 创建设备
deviceGroup.GET("", a.deviceController.ListDevices) // 获取设备列表 deviceGroup.GET("", a.deviceController.ListDevices) // 获取设备列表
deviceGroup.GET("/:id", a.deviceController.GetDevice) // 获取单个设备 deviceGroup.GET("/:id", a.deviceController.GetDevice) // 获取单个设备
deviceGroup.PUT("/:id", a.deviceController.UpdateDevice) // 更新设备 deviceGroup.PUT("/:id", a.deviceController.UpdateDevice) // 更新设备
deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice) // 删除设备 deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice) // 删除设备
deviceGroup.POST("/manual-control/:id", a.deviceController.ManualControl) // 手动控制设备
} }
a.logger.Info("设备相关接口注册成功 (需要认证和审计)") a.logger.Info("设备相关接口注册成功 (需要认证和审计)")
@@ -236,7 +239,7 @@ func (a *API) setupRoutes() {
pigBatchGroup.DELETE("/:id", a.pigBatchController.DeletePigBatch) // 删除猪群 pigBatchGroup.DELETE("/:id", a.pigBatchController.DeletePigBatch) // 删除猪群
pigBatchGroup.POST("/assign-pens/:id", a.pigBatchController.AssignEmptyPensToBatch) // 为猪群分配空栏 pigBatchGroup.POST("/assign-pens/:id", a.pigBatchController.AssignEmptyPensToBatch) // 为猪群分配空栏
pigBatchGroup.POST("/reclassify-pen/:fromBatchID", a.pigBatchController.ReclassifyPenToNewBatch) // 将猪栏划拨到新群 pigBatchGroup.POST("/reclassify-pen/:fromBatchID", a.pigBatchController.ReclassifyPenToNewBatch) // 将猪栏划拨到新群
pigBatchGroup.DELETE("/remove-pen/:penID/:batchID", a.pigBatchController.RemoveEmptyPenFromBatch) // 从猪群移除空栏 penGroup.DELETE("/remove-pen/:penID/:batchID", a.pigBatchController.RemoveEmptyPenFromBatch) // 从猪群移除空栏
pigBatchGroup.POST("/move-pigs-into-pen/:id", a.pigBatchController.MovePigsIntoPen) // 将猪只从“虚拟库存”移入指定猪栏 pigBatchGroup.POST("/move-pigs-into-pen/:id", a.pigBatchController.MovePigsIntoPen) // 将猪只从“虚拟库存”移入指定猪栏
pigBatchGroup.POST("/sell-pigs/:id", a.pigBatchController.SellPigs) // 处理卖猪业务 pigBatchGroup.POST("/sell-pigs/:id", a.pigBatchController.SellPigs) // 处理卖猪业务
pigBatchGroup.POST("/buy-pigs/:id", a.pigBatchController.BuyPigs) // 处理买猪业务 pigBatchGroup.POST("/buy-pigs/:id", a.pigBatchController.BuyPigs) // 处理买猪业务

View File

@@ -8,6 +8,7 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller" "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/dto"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" "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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
@@ -19,7 +20,8 @@ import (
type Controller struct { type Controller struct {
deviceRepo repository.DeviceRepository deviceRepo repository.DeviceRepository
areaControllerRepo repository.AreaControllerRepository areaControllerRepo repository.AreaControllerRepository
deviceTemplateRepo repository.DeviceTemplateRepository // 设备模板仓库 deviceTemplateRepo repository.DeviceTemplateRepository
deviceService device.Service
logger *logs.Logger logger *logs.Logger
} }
@@ -27,13 +29,15 @@ type Controller struct {
func NewController( func NewController(
deviceRepo repository.DeviceRepository, deviceRepo repository.DeviceRepository,
areaControllerRepo repository.AreaControllerRepository, areaControllerRepo repository.AreaControllerRepository,
deviceTemplateRepo repository.DeviceTemplateRepository, // 注入设备模板仓库 deviceTemplateRepo repository.DeviceTemplateRepository,
deviceService device.Service,
logger *logs.Logger, logger *logs.Logger,
) *Controller { ) *Controller {
return &Controller{ return &Controller{
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
areaControllerRepo: areaControllerRepo, areaControllerRepo: areaControllerRepo,
deviceTemplateRepo: deviceTemplateRepo, deviceTemplateRepo: deviceTemplateRepo,
deviceService: deviceService,
logger: logger, logger: logger,
} }
} }
@@ -298,6 +302,67 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) {
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID) controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID)
} }
// ManualControl godoc
// @Summary 手动控制设备
// @Description 根据设备ID和指定的动作开启或关闭来手动控制设备
// @Tags 设备管理
// @Accept json
// @Produce json
// @Param id path string true "设备ID"
// @Param manualControl body dto.ManualControlDeviceRequest true "手动控制指令"
// @Success 200 {object} controller.Response
// @Router /api/v1/devices/manual-control/{id} [post]
func (c *Controller) ManualControl(ctx *gin.Context) {
const actionType = "手动控制设备"
deviceID := ctx.Param("id")
var req dto.ManualControlDeviceRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
return
}
dev, err := c.deviceRepo.FindByIDString(deviceID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
return
}
if strings.Contains(err.Error(), "无效的设备ID格式") {
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "设备ID格式错误", deviceID)
return
}
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, deviceID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "手动控制失败: "+err.Error(), actionType, "数据库查询失败", deviceID)
return
}
c.logger.Infof("%s: 接收到指令, 设备ID: %s, 动作: %s", actionType, deviceID, req.Action)
if req.Action == nil {
err = c.deviceService.Collect(dev.AreaControllerID, []*models.Device{dev})
if err != nil {
c.logger.Errorf("%s: 获取设备状态失败: %v, 设备ID: %s", actionType, err, deviceID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备状态失败: "+err.Error(), actionType, "获取设备状态失败", deviceID)
}
} else {
action := device.DeviceActionStart
if *req.Action == "off" {
action = device.DeviceActionStop
}
err = c.deviceService.Switch(dev, action)
if err != nil {
c.logger.Errorf("%s: 设备控制失败: %v, 设备ID: %s", actionType, err, deviceID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备控制失败: "+err.Error(), actionType, "设备控制失败", deviceID)
return
}
}
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "指令已发送", map[string]interface{}{"device_id": deviceID}, actionType, "指令发送成功", gin.H{"device_id": deviceID, "action": req.Action})
}
// --- Controller Methods: Area Controllers --- // --- Controller Methods: Area Controllers ---
// CreateAreaController godoc // CreateAreaController godoc

View File

@@ -20,6 +20,12 @@ type UpdateDeviceRequest struct {
Properties map[string]interface{} `json:"properties,omitempty"` Properties map[string]interface{} `json:"properties,omitempty"`
} }
// ManualControlDeviceRequest 定义了手动控制设备时需要传入的参数
type ManualControlDeviceRequest struct {
// Action 不传表示这是一个传感器, 会触发一次采集
Action *string `json:"action" binding:"oneof=on off"`
}
// CreateAreaControllerRequest 定义了创建区域主控时需要传入的参数 // CreateAreaControllerRequest 定义了创建区域主控时需要传入的参数
type CreateAreaControllerRequest struct { type CreateAreaControllerRequest struct {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`

View File

@@ -163,6 +163,7 @@ func NewApplication(configPath string) (*Application, error) {
userActionLogRepo, userActionLogRepo,
tokenService, tokenService,
auditService, auditService,
generalDeviceService,
listenHandler, listenHandler,
analysisPlanTaskManager, analysisPlanTaskManager,
) )