Files
pig-farm-controller/internal/app/service/device_service.go
2025-11-05 19:57:30 +08:00

411 lines
13 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 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 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
type DeviceService interface {
CreateDevice(ctx context.Context, req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error)
GetDevice(ctx context.Context, id uint) (*dto.DeviceResponse, error)
ListDevices(ctx context.Context) ([]*dto.DeviceResponse, error)
UpdateDevice(ctx context.Context, id uint, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error)
DeleteDevice(ctx context.Context, id uint) error
ManualControl(ctx context.Context, id uint, req *dto.ManualControlDeviceRequest) error
CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error)
GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error)
ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error)
UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error)
DeleteAreaController(ctx context.Context, id uint) error
CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error)
ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error)
UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
DeleteDeviceTemplate(ctx context.Context, id uint) error
}
// deviceService 是 DeviceService 接口的具体实现。
type deviceService struct {
ctx context.Context
deviceRepo repository.DeviceRepository
areaControllerRepo repository.AreaControllerRepository
deviceTemplateRepo repository.DeviceTemplateRepository
deviceDomainSvc device.Service
}
// NewDeviceService 创建一个新的 DeviceService 实例。
func NewDeviceService(
ctx context.Context,
deviceRepo repository.DeviceRepository,
areaControllerRepo repository.AreaControllerRepository,
deviceTemplateRepo repository.DeviceTemplateRepository,
deviceDomainSvc device.Service,
) DeviceService {
return &deviceService{
ctx: ctx,
deviceRepo: deviceRepo,
areaControllerRepo: areaControllerRepo,
deviceTemplateRepo: deviceTemplateRepo,
deviceDomainSvc: deviceDomainSvc,
}
}
// --- 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 uint) (*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 uint, 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 uint) 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)
if err != nil {
// 如果检查过程中发生数据库错误,则返回错误
return fmt.Errorf("检查设备使用情况失败: %w", err)
}
if inUse {
// 如果设备正在被使用,则返回特定的业务错误
return ErrDeviceInUse
}
// 只有在未被使用时,才执行删除操作
return s.deviceRepo.Delete(serviceCtx, id)
}
func (s *deviceService) ManualControl(ctx context.Context, id uint, 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)
}
}
// --- Area Controllers ---
func (s *deviceService) CreateAreaController(ctx context.Context, req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateAreaController")
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(serviceCtx, ac); err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(ac)
}
func (s *deviceService) GetAreaController(ctx context.Context, id uint) (*dto.AreaControllerResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetAreaController")
ac, err := s.areaControllerRepo.FindByID(serviceCtx, id)
if err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(ac)
}
func (s *deviceService) ListAreaControllers(ctx context.Context) ([]*dto.AreaControllerResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListAreaControllers")
acs, err := s.areaControllerRepo.ListAll(serviceCtx)
if err != nil {
return nil, err
}
return dto.NewListAreaControllerResponse(acs)
}
func (s *deviceService) UpdateAreaController(ctx context.Context, id uint, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateAreaController")
existingAC, err := s.areaControllerRepo.FindByID(serviceCtx, id)
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(serviceCtx, existingAC); err != nil {
return nil, err
}
return dto.NewAreaControllerResponse(existingAC)
}
func (s *deviceService) DeleteAreaController(ctx context.Context, id uint) error {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteAreaController")
// 1. 检查是否存在
_, err := s.areaControllerRepo.FindByID(serviceCtx, id)
if err != nil {
return err // 如果未找到gorm会返回 ErrRecordNotFound
}
// 2. 检查是否被使用(业务逻辑)
inUse, err := s.deviceRepo.IsAreaControllerInUse(serviceCtx, id)
if err != nil {
return err // 返回数据库检查错误
}
if inUse {
return ErrAreaControllerInUse // 返回业务错误
}
// 3. 执行删除
return s.areaControllerRepo.Delete(serviceCtx, id)
}
// --- Device Templates ---
func (s *deviceService) CreateDeviceTemplate(ctx context.Context, req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateDeviceTemplate")
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(serviceCtx, deviceTemplate); err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(deviceTemplate)
}
func (s *deviceService) GetDeviceTemplate(ctx context.Context, id uint) (*dto.DeviceTemplateResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetDeviceTemplate")
deviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
if err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(deviceTemplate)
}
func (s *deviceService) ListDeviceTemplates(ctx context.Context) ([]*dto.DeviceTemplateResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListDeviceTemplates")
deviceTemplates, err := s.deviceTemplateRepo.ListAll(serviceCtx)
if err != nil {
return nil, err
}
return dto.NewListDeviceTemplateResponse(deviceTemplates)
}
func (s *deviceService) UpdateDeviceTemplate(ctx context.Context, id uint, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateDeviceTemplate")
existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
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(serviceCtx, existingDeviceTemplate); err != nil {
return nil, err
}
return dto.NewDeviceTemplateResponse(existingDeviceTemplate)
}
func (s *deviceService) DeleteDeviceTemplate(ctx context.Context, id uint) error {
serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteDeviceTemplate")
// 1. 检查是否存在
_, err := s.deviceTemplateRepo.FindByID(serviceCtx, id)
if err != nil {
return err
}
// 2. 检查是否被使用(业务逻辑)
inUse, err := s.deviceTemplateRepo.IsInUse(serviceCtx, id)
if err != nil {
return err
}
if inUse {
return ErrDeviceTemplateInUse // 返回业务错误
}
// 3. 执行删除
return s.deviceTemplateRepo.Delete(serviceCtx, id)
}