Files
pig-farm-controller/internal/app/service/device_service.go
2025-11-01 22:43:34 +08:00

432 lines
12 KiB
Go

package service
import (
"encoding/json"
"errors"
"strconv"
"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/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
)
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
type DeviceService interface {
CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error)
GetDevice(id string) (*dto.DeviceResponse, error)
ListDevices() ([]*dto.DeviceResponse, error)
UpdateDevice(id string, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error)
DeleteDevice(id string) error
ManualControl(id string, req *dto.ManualControlDeviceRequest) error
CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error)
GetAreaController(id string) (*dto.AreaControllerResponse, error)
ListAreaControllers() ([]*dto.AreaControllerResponse, error)
UpdateAreaController(id string, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error)
DeleteAreaController(id string) error
CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
GetDeviceTemplate(id string) (*dto.DeviceTemplateResponse, error)
ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error)
UpdateDeviceTemplate(id string, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
DeleteDeviceTemplate(id string) error
}
// deviceService 是 DeviceService 接口的具体实现。
type deviceService struct {
deviceRepo repository.DeviceRepository
areaControllerRepo repository.AreaControllerRepository
deviceTemplateRepo repository.DeviceTemplateRepository
planRepo repository.PlanRepository // 新增计划仓库依赖
deviceDomainSvc device.Service // 依赖领域服务
}
// NewDeviceService 创建一个新的 DeviceService 实例。
func NewDeviceService(
deviceRepo repository.DeviceRepository,
areaControllerRepo repository.AreaControllerRepository,
deviceTemplateRepo repository.DeviceTemplateRepository,
planRepo repository.PlanRepository,
deviceDomainSvc device.Service,
) DeviceService {
return &deviceService{
deviceRepo: deviceRepo,
areaControllerRepo: areaControllerRepo,
deviceTemplateRepo: deviceTemplateRepo,
planRepo: planRepo,
deviceDomainSvc: deviceDomainSvc,
}
}
func (s *deviceService) isDeviceInUseByActivePlan(deviceID uint) (bool, error) {
plans, err := s.planRepo.FindPlansWithPendingTasks()
if err != nil {
return false, fmt.Errorf("查询活动计划失败: %w", err)
}
for _, plan := range plans {
tasks, err := s.planRepo.FlattenPlanTasks(plan.ID)
if err != nil {
return false, fmt.Errorf("展开计划 %d 的任务失败: %w", plan.ID, err)
}
for _, task := range tasks {
// 假设任务参数中设备ID的键是 "device_id"
var params map[string]interface{}
if err := json.Unmarshal(task.Parameters, &params); err != nil {
// 无法解析参数,跳过此任务
continue
}
if paramDeviceID, ok := params["device_id"]; ok {
if floatDeviceID, ok := paramDeviceID.(float64); ok && uint(floatDeviceID) == deviceID {
return true, nil
}
}
}
}
return false, nil
}
// --- Devices ---
func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) {
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(device); err != nil {
return nil, err
}
createdDevice, err := s.deviceRepo.FindByID(device.ID)
if err != nil {
return nil, err
}
return dto.NewDeviceResponse(createdDevice)
}
func (s *deviceService) GetDevice(id string) (*dto.DeviceResponse, error) {
device, err := s.deviceRepo.FindByIDString(id)
if err != nil {
return nil, err
}
return dto.NewDeviceResponse(device)
}
func (s *deviceService) ListDevices() ([]*dto.DeviceResponse, error) {
devices, err := s.deviceRepo.ListAll()
if err != nil {
return nil, err
}
return dto.NewListDeviceResponse(devices)
}
func (s *deviceService) UpdateDevice(id string, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) {
existingDevice, err := s.deviceRepo.FindByIDString(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(existingDevice); err != nil {
return nil, err
}
updatedDevice, err := s.deviceRepo.FindByID(existingDevice.ID)
if err != nil {
return nil, err
}
return dto.NewDeviceResponse(updatedDevice)
}
func (s *deviceService) DeleteDevice(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return err
}
// Check if device exists before deleting
_, err = s.deviceRepo.FindByID(uint(idUint))
if err != nil {
return err
}
// 检查设备是否被活动计划使用
if inUse, err := s.isDeviceInUseByActivePlan(uint(idUint)); err != nil {
return fmt.Errorf("检查设备是否被计划使用失败: %w", err)
} else if inUse {
return errors.New("设备正在被活动计划使用,无法删除")
}
return s.deviceRepo.Delete(uint(idUint))
}
func (s *deviceService) ManualControl(id string, req *dto.ManualControlDeviceRequest) error {
dev, err := s.deviceRepo.FindByIDString(id)
if err != nil {
return err
}
if req.Action == nil {
return s.deviceDomainSvc.Collect(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(dev, action)
}
}
// --- Area Controllers ---
func (s *deviceService) CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
return nil, err
}
ac := &models.AreaController{
Name: req.Name,
NetworkID: req.NetworkID,
Location: req.Location,
Properties: propertiesJSON,
}
if err := ac.SelfCheck(); err != nil {
return nil, err
}
if err := s.areaControllerRepo.Create(ac); err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(ac)
}
func (s *deviceService) GetAreaController(id string) (*dto.AreaControllerResponse, error) {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, err
}
ac, err := s.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(ac)
}
func (s *deviceService) ListAreaControllers() ([]*dto.AreaControllerResponse, error) {
acs, err := s.areaControllerRepo.ListAll()
if err != nil {
return nil, err
}
return dto.NewListAreaControllerResponse(acs)
}
func (s *deviceService) UpdateAreaController(id string, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, err
}
existingAC, err := s.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
return nil, err
}
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
return nil, err
}
existingAC.Name = req.Name
existingAC.NetworkID = req.NetworkID
existingAC.Location = req.Location
existingAC.Properties = propertiesJSON
if err := existingAC.SelfCheck(); err != nil {
return nil, err
}
if err := s.areaControllerRepo.Update(existingAC); err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(existingAC)
}
func (s *deviceService) DeleteAreaController(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return err
}
_, err = s.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
return err
}
// 检查是否有设备正在使用此区域主控
devices, err := s.deviceRepo.ListByAreaControllerID(uint(idUint))
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if len(devices) > 0 {
return errors.New("区域主控正在被设备使用,无法删除")
}
return s.areaControllerRepo.Delete(uint(idUint))
}
// --- Device Templates ---
func (s *deviceService) CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
commandsJSON, err := json.Marshal(req.Commands)
if err != nil {
return nil, err
}
valuesJSON, err := json.Marshal(req.Values)
if err != nil {
return nil, err
}
deviceTemplate := &models.DeviceTemplate{
Name: req.Name,
Manufacturer: req.Manufacturer,
Description: req.Description,
Category: req.Category,
Commands: commandsJSON,
Values: valuesJSON,
}
if err := deviceTemplate.SelfCheck(); err != nil {
return nil, err
}
if err := s.deviceTemplateRepo.Create(deviceTemplate); err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(deviceTemplate)
}
func (s *deviceService) GetDeviceTemplate(id string) (*dto.DeviceTemplateResponse, error) {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, err
}
deviceTemplate, err := s.deviceTemplateRepo.FindByID(uint(idUint))
if err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(deviceTemplate)
}
func (s *deviceService) ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error) {
deviceTemplates, err := s.deviceTemplateRepo.ListAll()
if err != nil {
return nil, err
}
return dto.NewListDeviceTemplateResponse(deviceTemplates)
}
func (s *deviceService) UpdateDeviceTemplate(id string, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, err
}
existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(uint(idUint))
if err != nil {
return nil, err
}
commandsJSON, err := json.Marshal(req.Commands)
if err != nil {
return nil, err
}
valuesJSON, err := json.Marshal(req.Values)
if err != nil {
return nil, err
}
existingDeviceTemplate.Name = req.Name
existingDeviceTemplate.Manufacturer = req.Manufacturer
existingDeviceTemplate.Description = req.Description
existingDeviceTemplate.Category = req.Category
existingDeviceTemplate.Commands = commandsJSON
existingDeviceTemplate.Values = valuesJSON
if err := existingDeviceTemplate.SelfCheck(); err != nil {
return nil, err
}
if err := s.deviceTemplateRepo.Update(existingDeviceTemplate); err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(existingDeviceTemplate)
}
func (s *deviceService) DeleteDeviceTemplate(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return err
}
_, err = s.deviceTemplateRepo.FindByID(uint(idUint))
if err != nil {
return err
}
// 检查是否有设备正在使用此模板
devices, err := s.deviceRepo.FindByDeviceTemplateID(uint(idUint))
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if len(devices) > 0 {
return errors.New("设备模板正在被设备使用,无法删除")
}
return s.deviceTemplateRepo.Delete(uint(idUint))
}