猪群管理聚合服务

This commit is contained in:
2025-10-05 16:37:12 +08:00
parent 740e14e6cc
commit 6d080d250d
4 changed files with 290 additions and 186 deletions

View File

@@ -0,0 +1,50 @@
package pig
import (
"errors"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
)
// --- 业务错误定义 ---
var (
// ErrPigBatchNotFound 表示当尝试访问一个不存在的猪批次时发生的错误。
ErrPigBatchNotFound = errors.New("指定的猪批次不存在")
// ErrPigBatchActive 表示当尝试对一个活跃的猪批次执行不允许的操作(如删除)时发生的错误。
ErrPigBatchActive = errors.New("活跃的猪批次不能被删除")
// ErrPigBatchNotActive 表示当猪批次不处于活跃状态,但执行了需要其活跃的操作时发生的错误。
ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏")
// ErrPenOccupiedByOtherBatch 表示当尝试将一个已经被其他批次占用的猪栏分配给新批次时发生的错误。
ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用")
// ErrPenStatusInvalidForAllocation 表示猪栏的当前状态(例如,'维修中')不允许被分配。
ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配")
// ErrPenNotFound 表示猪栏不存在
ErrPenNotFound = errors.New("指定的猪栏不存在")
)
// --- 领域服务接口 ---
// PigBatchService 定义了猪批次管理的核心业务逻辑接口。
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
type PigBatchService interface {
// CreatePigBatch 创建一个新的猪批次。
CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
// GetPigBatch 根据ID获取单个猪批次的详细信息。
GetPigBatch(id uint) (*models.PigBatch, error)
// UpdatePigBatch 更新一个已存在的猪批次信息。
UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
// DeletePigBatch 删除一个指定的猪批次。
// 实现时需要包含业务规则校验,例如,活跃的批次不能被删除。
DeletePigBatch(id uint) error
// ListPigBatches 根据是否活跃的状态,列出所有符合条件的猪批次。
ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
// UpdatePigBatchPens 负责原子性地更新一个猪批次所关联的所有猪栏。
// 它会处理猪栏的添加、移除,并确保数据的一致性。
UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error
}

View File

@@ -0,0 +1,192 @@
package pig
import (
"errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm"
)
// --- 领域服务实现 ---
// pigBatchService 是 PigBatchService 接口的具体实现。
// 它封装了业务逻辑所需的所有依赖,如数据库仓库和工作单元。
type pigBatchService struct {
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
pigFarmRepo repository.PigFarmRepository // 猪场资产仓库 (包含猪栏操作)
uow repository.UnitOfWork // 工作单元,用于管理事务
}
// NewPigBatchService 是 pigBatchService 的构造函数。
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
func NewPigBatchService(
pigBatchRepo repository.PigBatchRepository,
pigFarmRepo repository.PigFarmRepository,
uow repository.UnitOfWork,
) PigBatchService {
return &pigBatchService{
pigBatchRepo: pigBatchRepo,
pigFarmRepo: pigFarmRepo,
uow: uow,
}
}
// CreatePigBatch 实现了创建猪批次的逻辑。
func (s *pigBatchService) CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
// 业务规则可以在这里添加,例如检查批次号是否唯一等
return s.pigBatchRepo.CreatePigBatch(batch)
}
// GetPigBatch 实现了获取单个猪批次的逻辑。
func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) {
batch, err := s.pigBatchRepo.GetPigBatchByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrPigBatchNotFound
}
return nil, err
}
return batch, nil
}
// UpdatePigBatch 实现了更新猪批次的逻辑。
func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
// 可以在这里添加更新前的业务校验
updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(batch)
if err != nil {
return nil, err
}
if rowsAffected == 0 {
return nil, ErrPigBatchNotFound // 如果没有行被更新,可能意味着记录不存在
}
return updatedBatch, nil
}
// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。
func (s *pigBatchService) DeletePigBatch(id uint) error {
// 1. 获取猪批次信息
batch, err := s.GetPigBatch(id) // 复用 GetPigBatch 方法
if err != nil {
return err // GetPigBatch 已经处理了 ErrRecordNotFound 的情况
}
// 2. 核心业务规则:检查猪批次是否为活跃状态
if batch.IsActive() {
return ErrPigBatchActive // 如果活跃,则不允许删除
}
// 3. 执行删除
rowsAffected, err := s.pigBatchRepo.DeletePigBatch(id)
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrPigBatchNotFound
}
return nil
}
// ListPigBatches 实现了批量查询猪批次的逻辑。
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) {
return s.pigBatchRepo.ListPigBatches(isActive)
}
// UpdatePigBatchPens 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
// 使用工作单元来确保操作的原子性
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃
pigBatch, err := s.pigFarmRepo.GetPigBatchByIDTx(tx, batchID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound
}
return fmt.Errorf("获取猪批次信息失败: %w", err)
}
if !pigBatch.IsActive() {
return ErrPigBatchNotActive
}
// 2. 获取当前关联的猪栏
currentPens, err := s.pigFarmRepo.GetPensByBatchID(tx, batchID)
if err != nil {
return fmt.Errorf("获取当前关联猪栏失败: %w", err)
}
currentPenMap := make(map[uint]models.Pen)
currentPenIDsSet := make(map[uint]struct{})
for _, pen := range currentPens {
currentPenMap[pen.ID] = pen
currentPenIDsSet[pen.ID] = struct{}{}
}
// 3. 构建期望猪栏ID集合
desiredPenIDsSet := make(map[uint]struct{})
for _, penID := range desiredPenIDs {
desiredPenIDsSet[penID] = struct{}{}
}
// 4. 计算需要添加和移除的猪栏
var pensToRemove []uint
for penID := range currentPenIDsSet {
if _, found := desiredPenIDsSet[penID]; !found {
pensToRemove = append(pensToRemove, penID)
}
}
var pensToAdd []uint
for _, penID := range desiredPenIDs {
if _, found := currentPenIDsSet[penID]; !found {
pensToAdd = append(pensToAdd, penID)
}
}
// 5. 处理移除猪栏的逻辑
for _, penID := range pensToRemove {
currentPen := currentPenMap[penID]
updates := make(map[string]interface{})
updates["pig_batch_id"] = nil
if currentPen.Status == models.PenStatusOccupied {
updates["status"] = models.PenStatusEmpty
}
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
return fmt.Errorf("移除猪栏 %d 失败: %w", penID, err)
}
}
// 6. 处理添加猪栏的逻辑
for _, penID := range pensToAdd {
actualPen, err := s.pigFarmRepo.GetPenByIDTx(tx, penID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
}
return fmt.Errorf("获取猪栏 %d 信息失败: %w", penID, err)
}
// 核心业务规则:校验猪栏是否可被分配
if actualPen.Status != models.PenStatusEmpty {
return fmt.Errorf("猪栏 %s 状态为 %s无法分配: %w", actualPen.PenNumber, actualPen.Status, ErrPenStatusInvalidForAllocation)
}
if actualPen.PigBatchID != nil {
return fmt.Errorf("猪栏 %s 已被其他批次 %d 使用: %w", actualPen.PenNumber, *actualPen.PigBatchID, ErrPenOccupiedByOtherBatch)
}
updates := map[string]interface{}{
"pig_batch_id": &batchID,
"status": models.PenStatusOccupied,
}
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
return fmt.Errorf("添加猪栏 %d 失败: %w", penID, err)
}
}
return nil
})
}