Compare commits

...

5 Commits

Author SHA1 Message Date
9c35372720 修改方法命名 2025-10-05 18:00:50 +08:00
b6e68e861b 调整仓库方法归属 2025-10-05 17:46:03 +08:00
b3933b6d63 调整仓库方法归属 2025-10-05 17:42:27 +08:00
01327eb8d2 猪群管理聚合服务 增加调栏管理 2025-10-05 17:30:39 +08:00
6d080d250d 猪群管理聚合服务 2025-10-05 16:37:12 +08:00
11 changed files with 710 additions and 345 deletions

View File

@@ -1,55 +1,37 @@
package service package service
import ( import (
"errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
domain_pig "git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
"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"
"gorm.io/gorm"
) )
var ( // PigBatchService 接口定义保持不变,继续作为应用层对外的契约。
ErrPigBatchNotFound = errors.New("指定的猪批次不存在")
ErrPigBatchActive = errors.New("活跃的猪批次不能被删除")
ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏")
ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用")
ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配")
ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
)
// PigBatchService 提供了猪批次管理的业务逻辑
type PigBatchService interface { type PigBatchService interface {
CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error)
GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error)
UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error)
DeletePigBatch(id uint) error DeletePigBatch(id uint) error
ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error)
// UpdatePigBatchPens 更新猪批次关联的猪栏
UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error
} }
// pigBatchService 的实现现在依赖于领域服务接口。
type pigBatchService struct { type pigBatchService struct {
logger *logs.Logger logger *logs.Logger
pigBatchRepo repository.PigBatchRepository // 猪批次仓库 domainService domain_pig.PigBatchService // 依赖注入领域服务
pigFarmRepo repository.PigFarmRepository // 猪场资产仓库 (包含猪栏操作)
uow repository.UnitOfWork // 工作单元,用于事务管理
} }
// NewPigBatchService 创建一个新的 PigBatchService 实例 // NewPigBatchService 构造函数被修改,以注入领域服务。
func NewPigBatchService(pigBatchRepo repository.PigBatchRepository, pigFarmRepo repository.PigFarmRepository, uow repository.UnitOfWork, logger *logs.Logger) PigBatchService { func NewPigBatchService(domainService domain_pig.PigBatchService, logger *logs.Logger) PigBatchService {
return &pigBatchService{ return &pigBatchService{
logger: logger, logger: logger,
pigBatchRepo: pigBatchRepo, domainService: domainService,
pigFarmRepo: pigFarmRepo,
uow: uow,
} }
} }
// toPigBatchResponseDTO 将 models.PigBatch 转换为 dto.PigBatchResponseDTO // toPigBatchResponseDTO 负责将领域模型转换为应用层DTO这个职责保留在应用层。
func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch) *dto.PigBatchResponseDTO { func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch) *dto.PigBatchResponseDTO {
if batch == nil { if batch == nil {
return nil return nil
@@ -62,14 +44,15 @@ func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch) *dto.Pig
EndDate: batch.EndDate, EndDate: batch.EndDate,
InitialCount: batch.InitialCount, InitialCount: batch.InitialCount,
Status: batch.Status, Status: batch.Status,
IsActive: batch.IsActive(), // 使用模型自带的 IsActive 方法 IsActive: batch.IsActive(),
CreateTime: batch.CreatedAt, CreateTime: batch.CreatedAt,
UpdateTime: batch.UpdatedAt, UpdateTime: batch.UpdatedAt,
} }
} }
// CreatePigBatch 处理创建猪批次的业务逻辑 // CreatePigBatch 现在将请求委托给领域服务处理。
func (s *pigBatchService) CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) { func (s *pigBatchService) CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) {
// 1. DTO -> 领域模型
batch := &models.PigBatch{ batch := &models.PigBatch{
BatchNumber: dto.BatchNumber, BatchNumber: dto.BatchNumber,
OriginType: dto.OriginType, OriginType: dto.OriginType,
@@ -78,41 +61,38 @@ func (s *pigBatchService) CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBa
Status: dto.Status, Status: dto.Status,
} }
createdBatch, err := s.pigBatchRepo.CreatePigBatch(batch) // 2. 调用领域服务
createdBatch, err := s.domainService.CreatePigBatch(batch)
if err != nil { if err != nil {
s.logger.Errorf("创建猪批次失败: %v", err) s.logger.Errorf("应用层: 创建猪批次失败: %v", err)
return nil, err return nil, mapDomainError(err)
} }
// 3. 领域模型 -> DTO
return s.toPigBatchResponseDTO(createdBatch), nil return s.toPigBatchResponseDTO(createdBatch), nil
} }
// GetPigBatch 处理获取单个猪批次的业务逻辑 // GetPigBatch 从领域服务获取数据并转换为DTO同时处理错误转换。
func (s *pigBatchService) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) { func (s *pigBatchService) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) {
batch, err := s.pigBatchRepo.GetPigBatchByID(id) batch, err := s.domainService.GetPigBatch(id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { s.logger.Warnf("应用层: 获取猪批次失败, ID: %d, 错误: %v", id, err)
return nil, ErrPigBatchNotFound return nil, mapDomainError(err)
}
s.logger.Errorf("获取猪批次失败ID: %d, 错误: %v", id, err)
return nil, err
} }
return s.toPigBatchResponseDTO(batch), nil return s.toPigBatchResponseDTO(batch), nil
} }
// UpdatePigBatch 处理更新猪批次的业务逻辑 // UpdatePigBatch 协调获取、更新和保存的流程,并处理错误转换。
func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) { func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) {
existingBatch, err := s.pigBatchRepo.GetPigBatchByID(id) // 1. 先获取最新的领域模型
existingBatch, err := s.domainService.GetPigBatch(id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { s.logger.Warnf("应用层: 更新猪批次失败,获取原批次信息错误, ID: %d, 错误: %v", id, err)
return nil, ErrPigBatchNotFound return nil, mapDomainError(err)
}
s.logger.Errorf("更新猪批次失败获取原批次信息错误ID: %d, 错误: %v", id, err)
return nil, err
} }
// 根据 DTO 中的非空字段更新模型 // 2. 将DTO中的变更应用到模型
if dto.BatchNumber != nil { if dto.BatchNumber != nil {
existingBatch.BatchNumber = *dto.BatchNumber existingBatch.BatchNumber = *dto.BatchNumber
} }
@@ -132,56 +112,33 @@ func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*
existingBatch.Status = *dto.Status existingBatch.Status = *dto.Status
} }
updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(existingBatch) // 3. 调用领域服务执行更新
updatedBatch, err := s.domainService.UpdatePigBatch(existingBatch)
if err != nil { if err != nil {
s.logger.Errorf("更新猪批次失败ID: %d, 错误: %v", id, err) s.logger.Errorf("应用层: 更新猪批次失败, ID: %d, 错误: %v", id, err)
return nil, err return nil, mapDomainError(err)
}
// 如果没有行受影响,则认为猪批次不存在
if rowsAffected == 0 {
return nil, ErrPigBatchNotFound
} }
// 4. 转换并返回结果
return s.toPigBatchResponseDTO(updatedBatch), nil return s.toPigBatchResponseDTO(updatedBatch), nil
} }
// DeletePigBatch 处理删除猪批次的业务逻辑 // DeletePigBatch 将删除操作委托给领域服务,并转换领域错误为应用层错误。
func (s *pigBatchService) DeletePigBatch(id uint) error { func (s *pigBatchService) DeletePigBatch(id uint) error {
// 1. 获取猪批次信息 err := s.domainService.DeletePigBatch(id)
batch, err := s.pigBatchRepo.GetPigBatchByID(id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { s.logger.Errorf("应用层: 删除猪批次失败, ID: %d, 错误: %v", id, err)
return ErrPigBatchNotFound return mapDomainError(err)
}
s.logger.Errorf("删除猪批次失败获取批次信息错误ID: %d, 错误: %v", id, err)
return err
} }
// 2. 检查猪批次是否活跃
if batch.IsActive() {
return ErrPigBatchActive // 如果活跃,则不允许删除
}
// 3. 执行删除操作
rowsAffected, err := s.pigBatchRepo.DeletePigBatch(id)
if err != nil {
s.logger.Errorf("删除猪批次失败ID: %d, 错误: %v", id, err)
return err
}
// 如果没有行受影响,则认为猪批次不存在
if rowsAffected == 0 {
return ErrPigBatchNotFound
}
return nil return nil
} }
// ListPigBatches 处理批量查询猪批次的业务逻辑 // ListPigBatches 从领域服务获取列表并进行转换。
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) { func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) {
batches, err := s.pigBatchRepo.ListPigBatches(isActive) batches, err := s.domainService.ListPigBatches(isActive)
if err != nil { if err != nil {
s.logger.Errorf("批量查询猪批次失败,错误: %v", err) s.logger.Errorf("应用层: 批量查询猪批次失败: %v", err)
return nil, err return nil, mapDomainError(err)
} }
var responseDTOs []*dto.PigBatchResponseDTO var responseDTOs []*dto.PigBatchResponseDTO
@@ -192,111 +149,12 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchRespons
return responseDTOs, nil return responseDTOs, nil
} }
// UpdatePigBatchPens 更新猪批次关联猪栏 // UpdatePigBatchPens 关联猪栏的复杂操作委托给领域服务,并处理错误转换。
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error { func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
// 使用工作单元执行事务 err := s.domainService.UpdatePigBatchPens(batchID, desiredPenIDs)
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { if err != nil {
// 1. 验证猪批次 s.logger.Errorf("应用层: 更新猪批次猪栏关联失败, 批次ID: %d, 错误: %v", batchID, err)
pigBatch, err := s.pigFarmRepo.GetPigBatchByIDTx(tx, batchID) return mapDomainError(err)
if err != nil { }
if errors.Is(err, gorm.ErrRecordNotFound) { return nil
return ErrPigBatchNotFound
}
s.logger.Errorf("更新猪批次猪栏失败: 获取猪批次信息错误ID: %d, 错误: %v", batchID, err)
return fmt.Errorf("获取猪批次信息失败: %w", err)
}
if !pigBatch.IsActive() {
return ErrPigBatchNotActive
}
// 2. 获取当前关联的猪栏
currentPens, err := s.pigFarmRepo.GetPensByBatchID(tx, batchID)
if err != nil {
s.logger.Errorf("更新猪批次猪栏失败: 获取当前关联猪栏错误批次ID: %d, 错误: %v", batchID, err)
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. 构建期望猪栏集合
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]
// 验证:确保猪栏确实与当前批次关联
if currentPen.PigBatchID == nil || *currentPen.PigBatchID != batchID {
s.logger.Warnf("尝试移除未与批次 %d 关联的猪栏 %d", batchID, penID)
return fmt.Errorf("猪栏 %d 未与该批次关联,无法移除", penID)
}
updates := make(map[string]interface{})
updates["pig_batch_id"] = nil // 总是将 PigBatchID 设为 nil
// 只有当猪栏当前状态是“使用中”时,才将其状态改回“空闲”
if currentPen.Status == models.PenStatusOccupied {
updates["status"] = models.PenStatusEmpty
}
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
s.logger.Errorf("更新猪批次猪栏失败: 移除猪栏 %d 失败: %v", penID, err)
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)
}
s.logger.Errorf("更新猪批次猪栏失败: 获取猪栏 %d 信息错误: %v", penID, err)
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, // 将 PigBatchID 设为当前批次ID的指针
"status": models.PenStatusOccupied, // 分配后,状态变为“使用中”
}
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
s.logger.Errorf("更新猪批次猪栏失败: 添加猪栏 %d 失败: %v", penID, err)
return fmt.Errorf("添加猪栏 %d 失败: %w", penID, err)
}
}
return nil
})
} }

View File

@@ -11,15 +11,6 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
var (
ErrHouseContainsPens = errors.New("无法删除包含猪栏的猪舍")
ErrHouseNotFound = errors.New("指定的猪舍不存在")
ErrPenInUse = errors.New("猪栏正在被活跃批次使用,无法删除")
ErrPenNotFound = errors.New("指定的猪栏不存在")
ErrPenStatusInvalidForOccupiedPen = errors.New("猪栏已被批次使用,无法设置为非使用中状态")
ErrPenStatusInvalidForUnoccupiedPen = errors.New("猪栏未被批次使用,无法设置为使用中状态")
)
// PigFarmService 提供了猪场资产管理的业务逻辑 // PigFarmService 提供了猪场资产管理的业务逻辑
type PigFarmService interface { type PigFarmService interface {
// PigHouse methods // PigHouse methods
@@ -40,17 +31,25 @@ type PigFarmService interface {
} }
type pigFarmService struct { type pigFarmService struct {
logger *logs.Logger logger *logs.Logger
repo repository.PigFarmRepository farmRepository repository.PigFarmRepository
uow repository.UnitOfWork // 工作单元,用于事务管理 penRepository repository.PigPenRepository
batchRepository repository.PigBatchRepository
uow repository.UnitOfWork // 工作单元,用于事务管理
} }
// NewPigFarmService 创建一个新的 PigFarmService 实例 // NewPigFarmService 创建一个新的 PigFarmService 实例
func NewPigFarmService(repo repository.PigFarmRepository, uow repository.UnitOfWork, logger *logs.Logger) PigFarmService { func NewPigFarmService(farmRepository repository.PigFarmRepository,
penRepository repository.PigPenRepository,
batchRepository repository.PigBatchRepository,
uow repository.UnitOfWork,
logger *logs.Logger) PigFarmService {
return &pigFarmService{ return &pigFarmService{
logger: logger, logger: logger,
repo: repo, farmRepository: farmRepository,
uow: uow, penRepository: penRepository,
batchRepository: batchRepository,
uow: uow,
} }
} }
@@ -61,16 +60,16 @@ func (s *pigFarmService) CreatePigHouse(name, description string) (*models.PigHo
Name: name, Name: name,
Description: description, Description: description,
} }
err := s.repo.CreatePigHouse(house) err := s.farmRepository.CreatePigHouse(house)
return house, err return house, err
} }
func (s *pigFarmService) GetPigHouseByID(id uint) (*models.PigHouse, error) { func (s *pigFarmService) GetPigHouseByID(id uint) (*models.PigHouse, error) {
return s.repo.GetPigHouseByID(id) return s.farmRepository.GetPigHouseByID(id)
} }
func (s *pigFarmService) ListPigHouses() ([]models.PigHouse, error) { func (s *pigFarmService) ListPigHouses() ([]models.PigHouse, error) {
return s.repo.ListPigHouses() return s.farmRepository.ListPigHouses()
} }
func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*models.PigHouse, error) { func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*models.PigHouse, error) {
@@ -79,7 +78,7 @@ func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*mod
Name: name, Name: name,
Description: description, Description: description,
} }
rowsAffected, err := s.repo.UpdatePigHouse(house) rowsAffected, err := s.farmRepository.UpdatePigHouse(house)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -87,12 +86,12 @@ func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*mod
return nil, ErrHouseNotFound return nil, ErrHouseNotFound
} }
// 返回更新后的完整信息 // 返回更新后的完整信息
return s.repo.GetPigHouseByID(id) return s.farmRepository.GetPigHouseByID(id)
} }
func (s *pigFarmService) DeletePigHouse(id uint) error { func (s *pigFarmService) DeletePigHouse(id uint) error {
// 业务逻辑:检查猪舍是否包含猪栏 // 业务逻辑:检查猪舍是否包含猪栏
penCount, err := s.repo.CountPensInHouse(id) penCount, err := s.farmRepository.CountPensInHouse(id)
if err != nil { if err != nil {
return err return err
} }
@@ -101,7 +100,7 @@ func (s *pigFarmService) DeletePigHouse(id uint) error {
} }
// 调用仓库层进行删除 // 调用仓库层进行删除
rowsAffected, err := s.repo.DeletePigHouse(id) rowsAffected, err := s.farmRepository.DeletePigHouse(id)
if err != nil { if err != nil {
return err return err
} }
@@ -115,7 +114,7 @@ func (s *pigFarmService) DeletePigHouse(id uint) error {
func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) (*models.Pen, error) { func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) (*models.Pen, error) {
// 业务逻辑:验证所属猪舍是否存在 // 业务逻辑:验证所属猪舍是否存在
_, err := s.repo.GetPigHouseByID(houseID) _, err := s.farmRepository.GetPigHouseByID(houseID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrHouseNotFound return nil, ErrHouseNotFound
@@ -129,21 +128,21 @@ func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int)
Capacity: capacity, Capacity: capacity,
Status: models.PenStatusEmpty, Status: models.PenStatusEmpty,
} }
err = s.repo.CreatePen(pen) err = s.penRepository.CreatePen(pen)
return pen, err return pen, err
} }
func (s *pigFarmService) GetPenByID(id uint) (*models.Pen, error) { func (s *pigFarmService) GetPenByID(id uint) (*models.Pen, error) {
return s.repo.GetPenByID(id) return s.penRepository.GetPenByID(id)
} }
func (s *pigFarmService) ListPens() ([]models.Pen, error) { func (s *pigFarmService) ListPens() ([]models.Pen, error) {
return s.repo.ListPens() return s.penRepository.ListPens()
} }
func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*models.Pen, error) { func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*models.Pen, error) {
// 业务逻辑:验证所属猪舍是否存在 // 业务逻辑:验证所属猪舍是否存在
_, err := s.repo.GetPigHouseByID(houseID) _, err := s.farmRepository.GetPigHouseByID(houseID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrHouseNotFound return nil, ErrHouseNotFound
@@ -158,7 +157,7 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa
Capacity: capacity, Capacity: capacity,
Status: status, Status: status,
} }
rowsAffected, err := s.repo.UpdatePen(pen) rowsAffected, err := s.penRepository.UpdatePen(pen)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -166,12 +165,12 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa
return nil, ErrPenNotFound return nil, ErrPenNotFound
} }
// 返回更新后的完整信息 // 返回更新后的完整信息
return s.repo.GetPenByID(id) return s.penRepository.GetPenByID(id)
} }
func (s *pigFarmService) DeletePen(id uint) error { func (s *pigFarmService) DeletePen(id uint) error {
// 业务逻辑:检查猪栏是否被活跃批次使用 // 业务逻辑:检查猪栏是否被活跃批次使用
pen, err := s.repo.GetPenByID(id) pen, err := s.penRepository.GetPenByID(id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound // 猪栏不存在 return ErrPenNotFound // 猪栏不存在
@@ -182,7 +181,7 @@ func (s *pigFarmService) DeletePen(id uint) error {
// 检查猪栏是否关联了活跃批次 // 检查猪栏是否关联了活跃批次
// 注意pen.PigBatchID 是指针类型,需要检查是否为 nil // 注意pen.PigBatchID 是指针类型,需要检查是否为 nil
if pen.PigBatchID != nil && *pen.PigBatchID != 0 { if pen.PigBatchID != nil && *pen.PigBatchID != 0 {
pigBatch, err := s.repo.GetPigBatchByID(*pen.PigBatchID) pigBatch, err := s.batchRepository.GetPigBatchByID(*pen.PigBatchID)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} }
@@ -193,7 +192,7 @@ func (s *pigFarmService) DeletePen(id uint) error {
} }
// 调用仓库层进行删除 // 调用仓库层进行删除
rowsAffected, err := s.repo.DeletePen(id) rowsAffected, err := s.penRepository.DeletePen(id)
if err != nil { if err != nil {
return err return err
} }
@@ -207,7 +206,7 @@ func (s *pigFarmService) DeletePen(id uint) error {
func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*models.Pen, error) { func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*models.Pen, error) {
var updatedPen *models.Pen var updatedPen *models.Pen
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
pen, err := s.repo.GetPenByIDTx(tx, id) pen, err := s.penRepository.GetPenByIDTx(tx, id)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound return ErrPenNotFound
@@ -237,13 +236,13 @@ func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*
"status": newStatus, "status": newStatus,
} }
if err := s.repo.UpdatePenFields(tx, id, updates); err != nil { if err := s.penRepository.UpdatePenFieldsTx(tx, id, updates); err != nil {
s.logger.Errorf("更新猪栏 %d 状态失败: %v", id, err) s.logger.Errorf("更新猪栏 %d 状态失败: %v", id, err)
return fmt.Errorf("更新猪栏 %d 状态失败: %w", id, err) return fmt.Errorf("更新猪栏 %d 状态失败: %w", id, err)
} }
// 获取更新后的猪栏信息 // 获取更新后的猪栏信息
updatedPen, err = s.repo.GetPenByIDTx(tx, id) updatedPen, err = s.penRepository.GetPenByIDTx(tx, id)
if err != nil { if err != nil {
s.logger.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %v", id, err) s.logger.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %v", id, err)
return fmt.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %w", id, err) return fmt.Errorf("更新猪栏状态后获取猪栏 %d 信息失败: %w", id, err)

View File

@@ -1 +1,49 @@
package service package service
import (
"errors"
domain_pig "git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
)
var (
ErrHouseContainsPens = errors.New("无法删除包含猪栏的猪舍")
ErrHouseNotFound = errors.New("指定的猪舍不存在")
ErrPenInUse = errors.New("猪栏正在被活跃批次使用,无法删除")
ErrPenNotFound = errors.New("指定的猪栏不存在")
ErrPenStatusInvalidForOccupiedPen = errors.New("猪栏已被批次使用,无法设置为非使用中状态")
ErrPenStatusInvalidForUnoccupiedPen = errors.New("猪栏未被批次使用,无法设置为使用中状态")
ErrPigBatchNotFound = errors.New("指定的猪批次不存在")
ErrPigBatchActive = errors.New("活跃的猪批次不能被删除")
ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏")
ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用")
ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配")
ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
)
// mapDomainError 将领域层的错误转换为应用服务层的公共错误。
func mapDomainError(err error) error {
if err == nil {
return nil
}
switch {
case errors.Is(err, domain_pig.ErrPigBatchNotFound):
return ErrPigBatchNotFound
case errors.Is(err, domain_pig.ErrPigBatchActive):
return ErrPigBatchActive
case errors.Is(err, domain_pig.ErrPigBatchNotActive):
return ErrPigBatchNotActive
case errors.Is(err, domain_pig.ErrPenOccupiedByOtherBatch):
return ErrPenOccupiedByOtherBatch
case errors.Is(err, domain_pig.ErrPenStatusInvalidForAllocation):
return ErrPenStatusInvalidForAllocation
case errors.Is(err, domain_pig.ErrPenNotAssociatedWithBatch):
return ErrPenNotAssociatedWithBatch
case errors.Is(err, domain_pig.ErrPenNotFound):
return ErrPenNotFound
// 可以添加更多领域错误到应用层错误的映射
default:
return err // 对于未知的领域错误,直接返回
}
}

View File

@@ -12,6 +12,7 @@ import (
"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"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device" "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
"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"
@@ -65,7 +66,6 @@ func NewApplication(configPath string) (*Application, error) {
areaControllerRepo := repository.NewGormAreaControllerRepository(storage.GetDB()) areaControllerRepo := repository.NewGormAreaControllerRepository(storage.GetDB())
deviceTemplateRepo := repository.NewGormDeviceTemplateRepository(storage.GetDB()) deviceTemplateRepo := repository.NewGormDeviceTemplateRepository(storage.GetDB())
planRepo := repository.NewGormPlanRepository(storage.GetDB()) planRepo := repository.NewGormPlanRepository(storage.GetDB())
pigFarmRepo := repository.NewGormPigFarmRepository(storage.GetDB())
pendingTaskRepo := repository.NewGormPendingTaskRepository(storage.GetDB()) pendingTaskRepo := repository.NewGormPendingTaskRepository(storage.GetDB())
executionLogRepo := repository.NewGormExecutionLogRepository(storage.GetDB()) executionLogRepo := repository.NewGormExecutionLogRepository(storage.GetDB())
sensorDataRepo := repository.NewGormSensorDataRepository(storage.GetDB()) sensorDataRepo := repository.NewGormSensorDataRepository(storage.GetDB())
@@ -73,13 +73,19 @@ func NewApplication(configPath string) (*Application, error) {
pendingCollectionRepo := repository.NewGormPendingCollectionRepository(storage.GetDB()) pendingCollectionRepo := repository.NewGormPendingCollectionRepository(storage.GetDB())
userActionLogRepo := repository.NewGormUserActionLogRepository(storage.GetDB()) userActionLogRepo := repository.NewGormUserActionLogRepository(storage.GetDB())
pigBatchRepo := repository.NewGormPigBatchRepository(storage.GetDB()) pigBatchRepo := repository.NewGormPigBatchRepository(storage.GetDB())
pigFarmRepo := repository.NewGormPigFarmRepository(storage.GetDB())
pigPenRepo := repository.NewGormPigPenRepository(storage.GetDB())
// 初始化事务管理器 // 初始化事务管理器
unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger) unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
// 初始化猪群管理领域
penTransferManager := pig.NewPenTransferManager(pigPenRepo)
pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, unitOfWork, penTransferManager)
// --- 业务逻辑处理器初始化 --- // --- 业务逻辑处理器初始化 ---
pigFarmService := service.NewPigFarmService(pigFarmRepo, unitOfWork, logger) pigFarmService := service.NewPigFarmService(pigFarmRepo, pigPenRepo, pigBatchRepo, unitOfWork, logger)
pigBatchService := service.NewPigBatchService(pigBatchRepo, pigFarmRepo, unitOfWork, logger) pigBatchService := service.NewPigBatchService(pigBatchDomain, logger)
// 初始化审计服务 // 初始化审计服务
auditService := audit.NewService(userActionLogRepo, logger) auditService := audit.NewService(userActionLogRepo, logger)

View File

@@ -0,0 +1,63 @@
package pig
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"gorm.io/gorm"
)
// PenTransferManager 定义了与猪只位置转移相关的底层数据库操作。
// 它是一个内部服务,被主服务 PigBatchService 调用。
type PenTransferManager interface {
// LogTransfer 在数据库中创建一条猪只迁移日志。
LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPenByIDTx 方法。
GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPensByBatchIDTx 方法。
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
// UpdatePenFields 更新一个猪栏的指定字段。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 UpdatePenFieldsTx 方法。
UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
}
// penTransferManager 是 PenTransferManager 接口的具体实现。
// 它作为调栏管理器,处理底层的数据库交互。
type penTransferManager struct {
penRepo repository.PigPenRepository
}
// NewPenTransferManager 是 penTransferManager 的构造函数。
func NewPenTransferManager(penRepo repository.PigPenRepository) PenTransferManager {
return &penTransferManager{
penRepo: penRepo,
}
}
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
func (s *penTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
// 直接使用事务对象创建记录。
return tx.Create(log).Error
}
// GetPenByID 实现了获取猪栏信息的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPenByIDTx。
func (s *penTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
return s.penRepo.GetPenByIDTx(tx, penID)
}
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPensByBatchIDTx。
func (s *penTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
return s.penRepo.GetPensByBatchIDTx(tx, batchID)
}
// UpdatePenFields 实现了更新猪栏字段的逻辑。
// 注意: 此处调用了一个假设存在的方法 UpdatePenFieldsTx。
func (s *penTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
return s.penRepo.UpdatePenFieldsTx(tx, penID, updates)
}

View File

@@ -0,0 +1,58 @@
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("指定的猪栏不存在")
// ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联
ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
)
// --- 领域服务接口 ---
// PigBatchService 定义了猪批次管理的核心业务逻辑接口。
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
type PigBatchService interface {
// TransferPigsWithinBatch 处理同一个猪群内部的调栏业务。
TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint) error
// TransferPigsAcrossBatches 处理跨猪群的调栏业务。
TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint) error
// 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,322 @@
package pig
import (
"errors"
"fmt"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"github.com/google/uuid"
"gorm.io/gorm"
)
// --- 领域服务实现 ---
// pigBatchService 是 PigBatchService 接口的具体实现。
// 它作为猪群领域的主服务,封装了所有业务逻辑。
type pigBatchService struct {
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
uow repository.UnitOfWork // 工作单元,用于管理事务
transferSvc PenTransferManager // 调栏子服务
}
// NewPigBatchService 是 pigBatchService 的构造函数。
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
func NewPigBatchService(
pigBatchRepo repository.PigBatchRepository,
uow repository.UnitOfWork,
transferSvc PenTransferManager,
) PigBatchService {
return &pigBatchService{
pigBatchRepo: pigBatchRepo,
uow: uow,
transferSvc: transferSvc,
}
}
// 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 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
// 它通过调用底层的 PenTransferManager 来执行数据库操作,从而保持了职责的清晰。
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
// 使用工作单元来确保操作的原子性
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃
// 注意: 此处依赖一个假设存在的 pigBatchRepo.GetPigBatchByIDTx 方法
pigBatch, err := s.pigBatchRepo.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.transferSvc.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.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
return fmt.Errorf("移除猪栏 %d 失败: %w", penID, err)
}
}
// 6. 处理添加猪栏的逻辑
for _, penID := range pensToAdd {
// 通过子服务获取猪栏信息
actualPen, err := s.transferSvc.GetPenByID(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.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
return fmt.Errorf("添加猪栏 %d 失败: %w", penID, err)
}
}
return nil
})
}
// --- 新增的调栏业务实现 ---
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType string) error {
// 1. 生成关联ID
correlationID := uuid.New().String()
// 2. 创建调出日志
logOut := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: fromBatchID,
PenID: fromPenID,
Quantity: -quantity, // 调出为负数
Type: transferType,
CorrelationID: correlationID,
}
// 3. 创建调入日志
logIn := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: toBatchID,
PenID: toPenID,
Quantity: quantity, // 调入为正数
Type: transferType,
CorrelationID: correlationID,
}
// 4. 调用子服务记录日志
if err := s.transferSvc.LogTransfer(tx, logOut); err != nil {
return fmt.Errorf("记录调出日志失败: %w", err)
}
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
return fmt.Errorf("记录调入日志失败: %w", err)
}
return nil
}
// TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。
func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint) error {
if fromPenID == toPenID {
return errors.New("源猪栏和目标猪栏不能相同")
}
if quantity == 0 {
return errors.New("迁移数量不能为零")
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 核心业务规则校验
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
if err != nil {
return fmt.Errorf("获取源猪栏信息失败: %w", err)
}
toPen, err := s.transferSvc.GetPenByID(tx, toPenID)
if err != nil {
return fmt.Errorf("获取目标猪栏信息失败: %w", err)
}
if fromPen.PigBatchID == nil || *fromPen.PigBatchID != batchID {
return fmt.Errorf("源猪栏 %d 不属于指定的猪群 %d", fromPenID, batchID)
}
if toPen.PigBatchID != nil && *toPen.PigBatchID != batchID {
return fmt.Errorf("目标猪栏 %d 已被其他猪群占用", toPenID)
}
// 2. 调用通用辅助方法执行日志记录
err = s.executeTransferAndLog(tx, batchID, batchID, fromPenID, toPenID, int(quantity), "群内调栏")
if err != nil {
return err
}
// 3. 群内调栏,猪群总数不变
return nil
})
}
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。
func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint) error {
if sourceBatchID == destBatchID {
return errors.New("源猪群和目标猪群不能相同")
}
if quantity == 0 {
return errors.New("迁移数量不能为零")
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 核心业务规则校验
sourceBatch, err := s.pigBatchRepo.GetPigBatchByID(sourceBatchID)
if err != nil {
return fmt.Errorf("获取源猪群信息失败: %w", err)
}
destBatch, err := s.pigBatchRepo.GetPigBatchByID(destBatchID)
if err != nil {
return fmt.Errorf("获取目标猪群信息失败: %w", err)
}
fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
if err != nil {
return fmt.Errorf("获取源猪栏信息失败: %w", err)
}
if fromPen.PigBatchID == nil || *fromPen.PigBatchID != sourceBatchID {
return fmt.Errorf("源猪栏 %d 不属于源猪群 %d", fromPenID, sourceBatchID)
}
// 2. 调用通用辅助方法执行日志记录
err = s.executeTransferAndLog(tx, sourceBatchID, destBatchID, fromPenID, toPenID, int(quantity), "跨群调栏")
if err != nil {
return err
}
// 3. 修改本聚合的数据(猪群总数)
sourceBatch.InitialCount -= int(quantity)
destBatch.InitialCount += int(quantity)
if _, _, err := s.pigBatchRepo.UpdatePigBatch(sourceBatch); err != nil {
return fmt.Errorf("更新源猪群数量失败: %w", err)
}
if _, _, err := s.pigBatchRepo.UpdatePigBatch(destBatch); err != nil {
return fmt.Errorf("更新目标猪群数量失败: %w", err)
}
return nil
})
}

View File

@@ -0,0 +1,19 @@
package models
import (
"time"
"gorm.io/gorm"
)
// PigTransferLog 记录了每一次猪只数量在猪栏间的变动事件。
// 它作为事件溯源的基础,用于推算任意时间点猪栏的猪只数量。
type PigTransferLog struct {
gorm.Model
TransferTime time.Time `json:"transfer_time"` // 迁移发生时间
PigBatchID uint `json:"pig_batch_id"` // 关联的猪群ID
PenID uint `json:"pen_id"` // 发生变动的猪栏ID
Quantity int `json:"quantity"` // 变动数量(正数表示增加,负数表示减少)
Type string `json:"type"` // 变动类型 (e.g., "群内调栏", "跨群调栏", "销售", "死亡", "新购入")
CorrelationID string `json:"correlation_id"` // 用于关联一次完整操作(如一次调栏会产生两条日志)
}

View File

@@ -9,6 +9,7 @@ import (
type PigBatchRepository interface { type PigBatchRepository interface {
CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
GetPigBatchByID(id uint) (*models.PigBatch, error) GetPigBatchByID(id uint) (*models.PigBatch, error)
GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error)
// UpdatePigBatch 更新一个猪批次,返回更新后的批次、受影响的行数和错误 // UpdatePigBatch 更新一个猪批次,返回更新后的批次、受影响的行数和错误
UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error)
// DeletePigBatch 根据ID删除一个猪批次返回受影响的行数和错误 // DeletePigBatch 根据ID删除一个猪批次返回受影响的行数和错误
@@ -36,11 +37,7 @@ func (r *gormPigBatchRepository) CreatePigBatch(batch *models.PigBatch) (*models
// GetPigBatchByID 根据ID获取单个猪批次 // GetPigBatchByID 根据ID获取单个猪批次
func (r *gormPigBatchRepository) GetPigBatchByID(id uint) (*models.PigBatch, error) { func (r *gormPigBatchRepository) GetPigBatchByID(id uint) (*models.PigBatch, error) {
var batch models.PigBatch return r.GetPigBatchByIDTx(r.db, id)
if err := r.db.First(&batch, id).Error; err != nil {
return nil, err
}
return &batch, nil
} }
// UpdatePigBatch 更新一个猪批次 // UpdatePigBatch 更新一个猪批次
@@ -83,3 +80,12 @@ func (r *gormPigBatchRepository) ListPigBatches(isActive *bool) ([]*models.PigBa
} }
return batches, nil return batches, nil
} }
// GetPigBatchByIDTx 在指定的事务中通过ID获取单个猪批次
func (r *gormPigBatchRepository) GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error) {
var batch models.PigBatch
if err := tx.First(&batch, id).Error; err != nil {
return nil, err
}
return &batch, nil
}

View File

@@ -16,28 +16,6 @@ type PigFarmRepository interface {
// DeletePigHouse 根据ID删除一个猪舍返回受影响的行数和错误 // DeletePigHouse 根据ID删除一个猪舍返回受影响的行数和错误
DeletePigHouse(id uint) (int64, error) DeletePigHouse(id uint) (int64, error)
CountPensInHouse(houseID uint) (int64, error) CountPensInHouse(houseID uint) (int64, error)
// Pen methods
CreatePen(pen *models.Pen) error
// GetPenByID 根据ID获取单个猪栏 (非事务性)
GetPenByID(id uint) (*models.Pen, error)
// GetPenByIDTx 根据ID获取单个猪栏 (事务性)
GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error)
ListPens() ([]models.Pen, error)
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
UpdatePen(pen *models.Pen) (int64, error)
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
DeletePen(id uint) (int64, error)
// GetPensByBatchID 根据批次ID获取所有关联的猪栏 (事务性)
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]models.Pen, error)
// UpdatePenFields 更新猪栏的指定字段 (事务性)
UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
// PigBatch methods
// GetPigBatchByID 根据ID获取单个猪批次 (非事务性)
GetPigBatchByID(id uint) (*models.PigBatch, error)
// GetPigBatchByIDTx 根据ID获取单个猪批次 (事务性)
GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error)
} }
// gormPigFarmRepository 是 PigFarmRepository 的 GORM 实现 // gormPigFarmRepository 是 PigFarmRepository 的 GORM 实现
@@ -99,92 +77,3 @@ func (r *gormPigFarmRepository) CountPensInHouse(houseID uint) (int64, error) {
err := r.db.Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error err := r.db.Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error
return count, err return count, err
} }
// --- Pen Implementation ---
// CreatePen 创建一个新的猪栏
func (r *gormPigFarmRepository) CreatePen(pen *models.Pen) error {
return r.db.Create(pen).Error
}
// GetPenByID 根据ID获取单个猪栏 (非事务性)
func (r *gormPigFarmRepository) GetPenByID(id uint) (*models.Pen, error) {
var pen models.Pen
if err := r.db.First(&pen, id).Error; err != nil {
return nil, err
}
return &pen, nil
}
// GetPenByIDTx 根据ID获取单个猪栏 (事务性)
func (r *gormPigFarmRepository) GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error) {
var pen models.Pen
if err := tx.First(&pen, id).Error; err != nil {
return nil, err
}
return &pen, nil
}
// ListPens 列出所有猪栏
func (r *gormPigFarmRepository) ListPens() ([]models.Pen, error) {
var pens []models.Pen
if err := r.db.Find(&pens).Error; err != nil {
return nil, err
}
return pens, nil
}
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
func (r *gormPigFarmRepository) UpdatePen(pen *models.Pen) (int64, error) {
result := r.db.Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen)
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
func (r *gormPigFarmRepository) DeletePen(id uint) (int64, error) {
result := r.db.Delete(&models.Pen{}, id)
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// GetPensByBatchID 根据批次ID获取所有关联的猪栏 (事务性)
func (r *gormPigFarmRepository) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]models.Pen, error) {
var pens []models.Pen
// 注意PigBatchID 是指针类型,需要处理 nil 值
result := tx.Where("pig_batch_id = ?", batchID).Find(&pens)
if result.Error != nil {
return nil, result.Error
}
return pens, nil
}
// UpdatePenFields 更新猪栏的指定字段 (事务性)
func (r *gormPigFarmRepository) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
result := tx.Model(&models.Pen{}).Where("id = ?", penID).Updates(updates)
return result.Error
}
// --- PigBatch Implementation ---
// GetPigBatchByID 根据ID获取单个猪批次 (非事务性)
func (r *gormPigFarmRepository) GetPigBatchByID(id uint) (*models.PigBatch, error) {
var batch models.PigBatch
if err := r.db.First(&batch, id).Error; err != nil {
return nil, err
}
return &batch, nil
}
// GetPigBatchByIDTx 根据ID获取单个猪批次 (事务性)
func (r *gormPigFarmRepository) GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error) {
var batch models.PigBatch
if err := tx.First(&batch, id).Error; err != nil {
return nil, err
}
return &batch, nil
}

View File

@@ -0,0 +1,97 @@
package repository
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigPenRepository 定义了与猪栏模型相关的数据库操作接口。
type PigPenRepository interface {
CreatePen(pen *models.Pen) error
// GetPenByID 根据ID获取单个猪栏 (非事务性)
GetPenByID(id uint) (*models.Pen, error)
// GetPenByIDTx 根据ID获取单个猪栏 (事务性)
GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error)
ListPens() ([]models.Pen, error)
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
UpdatePen(pen *models.Pen) (int64, error)
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
DeletePen(id uint) (int64, error)
// GetPensByBatchIDTx 根据批次ID获取所有关联的猪栏 (事务性)
GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
// UpdatePenFieldsTx 更新猪栏的指定字段 (事务性)
UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error
}
// gormPigPenRepository 是 PigPenRepository 接口的 GORM 实现。
type gormPigPenRepository struct {
db *gorm.DB
}
// NewGormPigPenRepository 创建一个新的 PigPenRepository GORM 实现实例。
func NewGormPigPenRepository(db *gorm.DB) PigPenRepository {
return &gormPigPenRepository{db: db}
}
// CreatePen 创建一个新的猪栏
func (r *gormPigPenRepository) CreatePen(pen *models.Pen) error {
return r.db.Create(pen).Error
}
// GetPenByID 根据ID获取单个猪栏 (非事务性)
func (r *gormPigPenRepository) GetPenByID(id uint) (*models.Pen, error) {
return r.GetPenByIDTx(r.db, id) // 非Tx方法直接调用Tx方法
}
// GetPenByIDTx 在指定的事务中通过ID获取单个猪栏信息。
func (r *gormPigPenRepository) GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error) {
var pen models.Pen
if err := tx.First(&pen, id).Error; err != nil {
return nil, err
}
return &pen, nil
}
// ListPens 列出所有猪栏
func (r *gormPigPenRepository) ListPens() ([]models.Pen, error) {
var pens []models.Pen
if err := r.db.Find(&pens).Error; err != nil {
return nil, err
}
return pens, nil
}
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
func (r *gormPigPenRepository) UpdatePen(pen *models.Pen) (int64, error) {
result := r.db.Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen)
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
func (r *gormPigPenRepository) DeletePen(id uint) (int64, error) {
result := r.db.Delete(&models.Pen{}, id)
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// GetPensByBatchIDTx 在指定的事务中,获取一个猪群当前关联的所有猪栏。
func (r *gormPigPenRepository) GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
var pens []*models.Pen
// 注意PigBatchID 是指针类型,需要处理 nil 值
result := tx.Where("pig_batch_id = ?", batchID).Find(&pens)
if result.Error != nil {
return nil, result.Error
}
return pens, nil
}
// UpdatePenFieldsTx 在指定的事务中,更新一个猪栏的指定字段。
func (r *gormPigPenRepository) UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
result := tx.Model(&models.Pen{}).Where("id = ?", penID).Updates(updates)
return result.Error
}