package service import ( "context" "encoding/json" "errors" "fmt" "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/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) var ( // ErrDeviceInUse 表示设备正在被任务使用,无法删除 ErrDeviceInUse = errors.New("设备正在被一个或多个任务使用,无法删除") // ErrAreaControllerInUse 表示区域主控正在被设备使用,无法删除 ErrAreaControllerInUse = errors.New("区域主控正在被一个或多个设备使用,无法删除") // ErrDeviceTemplateInUse 表示设备模板正在被设备使用,无法删除 ErrDeviceTemplateInUse = errors.New("设备模板正在被一个或多个设备使用,无法删除") ) // DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。 type DeviceService interface { CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) GetDevice(ctx context.Context, id uint32) (*dto.DeviceResponse, error) ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error) UpdateDevice(ctx context.Context, id uint32, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) DeleteDevice(ctx context.Context, id uint32) error ManualControl(ctx context.Context, id uint32, req *dto.ManualControlDeviceRequest) error } // deviceService 是 DeviceService 接口的具体实现。 type deviceService struct { ctx context.Context deviceRepo repository.DeviceRepository deviceDomainSvc device.DeviceOperator thresholdAlarmService ThresholdAlarmService } // NewDeviceService 创建一个新的 DeviceService 实例。 func NewDeviceService( ctx context.Context, deviceRepo repository.DeviceRepository, deviceDomainSvc device.DeviceOperator, thresholdAlarmService ThresholdAlarmService, ) DeviceService { return &deviceService{ ctx: ctx, deviceRepo: deviceRepo, deviceDomainSvc: deviceDomainSvc, thresholdAlarmService: thresholdAlarmService, } } // --- Devices --- func (s *deviceService) CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateDevice") propertiesJSON, err := json.Marshal(req.Properties) if err != nil { return nil, err // Consider wrapping this error for better context } device := &models.Device{ Name: req.Name, DeviceTemplateID: req.DeviceTemplateID, AreaControllerID: req.AreaControllerID, Location: req.Location, Properties: propertiesJSON, } if err := device.SelfCheck(); err != nil { return nil, err } if err := s.deviceRepo.Create(serviceCtx, device); err != nil { return nil, err } createdDevice, err := s.deviceRepo.FindByID(serviceCtx, device.ID) if err != nil { return nil, err } return dto.NewDeviceResponse(createdDevice) } func (s *deviceService) GetDevice(ctx context.Context, id uint32) (*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDevice") device, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } return dto.NewDeviceResponse(device) } func (s *deviceService) ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListDevices") devices, err := s.deviceRepo.ListAll(serviceCtx) if err != nil { return nil, err } return dto.NewListDeviceResponse(devices) } func (s *deviceService) UpdateDevice(ctx context.Context, id uint32, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDevice") existingDevice, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return nil, err } propertiesJSON, err := json.Marshal(req.Properties) if err != nil { return nil, err } existingDevice.Name = req.Name existingDevice.DeviceTemplateID = req.DeviceTemplateID existingDevice.AreaControllerID = req.AreaControllerID existingDevice.Location = req.Location existingDevice.Properties = propertiesJSON if err := existingDevice.SelfCheck(); err != nil { return nil, err } if err := s.deviceRepo.Update(serviceCtx, existingDevice); err != nil { return nil, err } updatedDevice, err := s.deviceRepo.FindByID(serviceCtx, existingDevice.ID) if err != nil { return nil, err } return dto.NewDeviceResponse(updatedDevice) } func (s *deviceService) DeleteDevice(ctx context.Context, id uint32) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDevice") // 检查设备是否存在 _, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return err // 如果未找到,会返回 gorm.ErrRecordNotFound } // 在删除前检查设备是否被任务使用 inUse, err := s.deviceRepo.IsDeviceInUse(serviceCtx, id, []models.TaskType{models.TaskTypeDeviceThresholdCheck}) if err != nil { // 如果检查过程中发生数据库错误,则返回错误 return fmt.Errorf("检查设备使用情况失败: %w", err) } if inUse { // 如果设备正在被使用,则返回特定的业务错误 return ErrDeviceInUse } // TODO 这个应该用事务处理 err = s.thresholdAlarmService.DeleteDeviceThresholdAlarmByDeviceID(serviceCtx, id) if err != nil { return fmt.Errorf("删除设备阈值告警失败: %w", err) } // 只有在未被使用时,才执行删除操作 return s.deviceRepo.Delete(serviceCtx, id) } func (s *deviceService) ManualControl(ctx context.Context, id uint32, req *dto.ManualControlDeviceRequest) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ManualControl") dev, err := s.deviceRepo.FindByID(serviceCtx, id) if err != nil { return err } if req.Action == nil { return s.deviceDomainSvc.Collect(serviceCtx, dev.AreaControllerID, []*models.Device{dev}) } else { action := device.DeviceActionStart switch *req.Action { case "off": action = device.DeviceActionStop case "on": action = device.DeviceActionStart default: return errors.New("invalid action") } return s.deviceDomainSvc.Switch(serviceCtx, dev, action) } }