需求文档
This commit is contained in:
		@@ -16,3 +16,4 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/50
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
1. [重构计划领域](./plan_service_refactor.md)
 | 
					1. [重构计划领域](./plan_service_refactor.md)
 | 
				
			||||||
2. [让任务可以提供自身使用设备](./add_get_device_id_configs_to_task.md)
 | 
					2. [让任务可以提供自身使用设备](./add_get_device_id_configs_to_task.md)
 | 
				
			||||||
 | 
					3. [现有计划管理逻辑迁移](./plan_service_refactor_to_domain.md)
 | 
				
			||||||
@@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					# 重构方案:将 `app/service/plan_service.go` 的核心逻辑迁移到 `domain/plan/plan_service.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 目标:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   `app/service/plan_service.go` (应用服务层): 仅负责接收 DTO、将 DTO 转换为领域实体、调用 `domain/plan/plan_service` 的领域方法,并将领域方法返回的领域实体转换为 DTO 返回。
 | 
				
			||||||
 | 
					*   `domain/plan/plan_service.go` (领域层): 封装所有与计划相关的业务逻辑、验证规则、状态管理以及对领域实体的查询操作。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 详细步骤:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 第一步:修改 `domain/plan/plan_service.go` (领域层)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **引入必要的依赖**:
 | 
				
			||||||
 | 
					    *   `repository.PlanRepository`:用于与计划数据存储交互。
 | 
				
			||||||
 | 
					    *   `repository.DeviceRepository`:如果计划逻辑中需要设备信息。
 | 
				
			||||||
 | 
					    *   `models.Plan`:领域实体。
 | 
				
			||||||
 | 
					    *   `errors` 和 `gorm.ErrRecordNotFound`:用于错误处理。
 | 
				
			||||||
 | 
					    *   `models.PlanTypeSystem`, `models.PlanStatusEnabled`, `models.PlanContentTypeSubPlans`, `models.PlanContentTypeTasks` 等常量。
 | 
				
			||||||
 | 
					    *   `git.huangwc.com/pig/pig-farm-controller/internal/infra/models`
 | 
				
			||||||
 | 
					    *   `git.huangwc.com/pig/pig-farm-controller/internal/infra/repository`
 | 
				
			||||||
 | 
					    *   `errors`
 | 
				
			||||||
 | 
					    *   `gorm.io/gorm`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2.  **定义领域层错误**: 将 `app/service/plan_service.go` 中定义的错误(`ErrPlanNotFound`, `ErrPlanCannotBeModified` 等)迁移到 `domain/plan/plan_service.go`,并根据领域层的语义进行调整。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    var (
 | 
				
			||||||
 | 
					    	// ErrPlanNotFound 表示未找到计划
 | 
				
			||||||
 | 
					    	ErrPlanNotFound = errors.New("计划不存在")
 | 
				
			||||||
 | 
					    	// ErrPlanCannotBeModified 表示计划不允许修改
 | 
				
			||||||
 | 
					    	ErrPlanCannotBeModified = errors.New("系统计划不允许修改")
 | 
				
			||||||
 | 
					    	// ErrPlanCannotBeDeleted 表示计划不允许删除
 | 
				
			||||||
 | 
					    	ErrPlanCannotBeDeleted = errors.New("系统计划不允许删除")
 | 
				
			||||||
 | 
					    	// ErrPlanCannotBeStarted 表示计划不允许手动启动
 | 
				
			||||||
 | 
					    	ErrPlanCannotBeStarted = errors.New("系统计划不允许手动启动")
 | 
				
			||||||
 | 
					    	// ErrPlanAlreadyEnabled 表示计划已处于启动状态
 | 
				
			||||||
 | 
					    	ErrPlanAlreadyEnabled = errors.New("计划已处于启动状态,无需重复操作")
 | 
				
			||||||
 | 
					    	// ErrPlanNotEnabled 表示计划未处于启动状态
 | 
				
			||||||
 | 
					    	ErrPlanNotEnabled = errors.New("计划当前不是启用状态")
 | 
				
			||||||
 | 
					    	// ErrPlanCannotBeStopped 表示计划不允许停止
 | 
				
			||||||
 | 
					    	ErrPlanCannotBeStopped = errors.New("系统计划不允许停止")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3.  **扩展 `plan.Service` 接口**:
 | 
				
			||||||
 | 
					    *   将 `app/service/plan_service.go` 中 `PlanService` 接口的所有方法(`CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)添加到 `domain/plan/Service` 接口中。
 | 
				
			||||||
 | 
					    *   这些方法的参数和返回值将直接使用领域实体(`*models.Plan`)或基本类型,而不是 DTO。例如:
 | 
				
			||||||
 | 
					        *   `CreatePlan(plan *models.Plan) (*models.Plan, error)`
 | 
				
			||||||
 | 
					        *   `GetPlanByID(id uint) (*models.Plan, error)`
 | 
				
			||||||
 | 
					        *   `ListPlans(opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)`
 | 
				
			||||||
 | 
					        *   `UpdatePlan(plan *models.Plan) (*models.Plan, error)`
 | 
				
			||||||
 | 
					        *   `DeletePlan(id uint) error`
 | 
				
			||||||
 | 
					        *   `StartPlan(id uint) error`
 | 
				
			||||||
 | 
					        *   `StopPlan(id uint) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.  **修改 `planServiceImpl` 结构体**:
 | 
				
			||||||
 | 
					    *   添加 `planRepo repository.PlanRepository` 字段。
 | 
				
			||||||
 | 
					    *   添加 `deviceRepo repository.DeviceRepository` 字段 (如果需要)。
 | 
				
			||||||
 | 
					    *   `analysisPlanTaskManager plan.AnalysisPlanTaskManager` 字段保持不变。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    type planServiceImpl struct {
 | 
				
			||||||
 | 
					    	executionManager ExecutionManager
 | 
				
			||||||
 | 
					    	taskManager      AnalysisPlanTaskManager
 | 
				
			||||||
 | 
					    	planRepo         repository.PlanRepository // 新增
 | 
				
			||||||
 | 
					    	// deviceRepo       repository.DeviceRepository // 如果需要,新增
 | 
				
			||||||
 | 
					    	logger           *logs.Logger
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5.  **实现 `plan.Service` 接口中的新方法**:
 | 
				
			||||||
 | 
					    *   将 `app/service/plan_service.go` 中 `planService` 的所有业务逻辑方法(`CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)的实现,迁移到 `domain/plan/planServiceImpl` 中。
 | 
				
			||||||
 | 
					    *   **关键修改点**:
 | 
				
			||||||
 | 
					        *   **参数和返回值**: 确保这些方法现在接收和返回的是 `*models.Plan` 或其他领域实体,而不是 DTO。
 | 
				
			||||||
 | 
					        *   **业务逻辑**: 保留所有的业务规则、验证和状态管理逻辑。
 | 
				
			||||||
 | 
					        *   **依赖**: 这些方法将直接调用 `planRepo` 和 `analysisPlanTaskManager`。
 | 
				
			||||||
 | 
					        *   **日志**: 日志记录保持不变,但可能需要调整日志信息以反映领域层的上下文。
 | 
				
			||||||
 | 
					        *   **错误处理**: 错误处理逻辑保持不变,但现在将返回领域层定义的错误。
 | 
				
			||||||
 | 
					        *   **ContentType 自动判断**: `CreatePlan` 和 `UpdatePlan` 中的 `ContentType` 自动判断逻辑应该保留在领域层。
 | 
				
			||||||
 | 
					        *   **执行计数器重置**: `UpdatePlan` 和 `StartPlan` 中的执行计数器重置逻辑应该保留在领域层。
 | 
				
			||||||
 | 
					        *   **系统计划限制**: 对系统计划的修改、删除、启动、停止限制逻辑应该保留在领域层。
 | 
				
			||||||
 | 
					        *   **验证和重排顺序**: `models.Plan` 的 `ValidateExecutionOrder()` 和 `ReorderSteps()` 方法的调用应该在 `CreatePlan` 和 `UpdatePlan` 方法的领域层实现中进行,而不是在 DTO 转换函数中。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6.  **修改 `NewPlanService` 函数**: 接收 `repository.PlanRepository` 和 `repository.DeviceRepository` (如果需要) 作为参数,并注入到 `planServiceImpl` 中。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    func NewPlanService(
 | 
				
			||||||
 | 
					    	executionManager ExecutionManager,
 | 
				
			||||||
 | 
					    	taskManager AnalysisPlanTaskManager,
 | 
				
			||||||
 | 
					    	planRepo repository.PlanRepository, // 新增
 | 
				
			||||||
 | 
					    	// deviceRepo repository.DeviceRepository, // 如果需要,新增
 | 
				
			||||||
 | 
					    	logger *logs.Logger,
 | 
				
			||||||
 | 
					    ) Service {
 | 
				
			||||||
 | 
					    	return &planServiceImpl{
 | 
				
			||||||
 | 
					    		executionManager: executionManager,
 | 
				
			||||||
 | 
					    		taskManager:      taskManager,
 | 
				
			||||||
 | 
					    		planRepo:         planRepo, // 注入
 | 
				
			||||||
 | 
					    		// deviceRepo:       deviceRepo, // 注入
 | 
				
			||||||
 | 
					    		logger:           logger,
 | 
				
			||||||
 | 
					    	}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 第二步:修改 `app/service/plan_service.go` (应用服务层)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **修改 `planService` 结构体**:
 | 
				
			||||||
 | 
					    *   移除 `planRepo repository.PlanRepository` 字段。
 | 
				
			||||||
 | 
					    *   将 `analysisPlanTaskManager plan.AnalysisPlanTaskManager` 字段替换为 `domainPlanService plan.Service`。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    type planService struct {
 | 
				
			||||||
 | 
					    	logger            *logs.Logger
 | 
				
			||||||
 | 
					    	// planRepo         repository.PlanRepository // 移除
 | 
				
			||||||
 | 
					    	domainPlanService plan.Service // 替换为领域层的服务接口
 | 
				
			||||||
 | 
					    	// analysisPlanTaskManager plan.AnalysisPlanTaskManager // 移除,由 domainPlanService 内部持有
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2.  **修改 `NewPlanService` 函数**:
 | 
				
			||||||
 | 
					    *   接收 `domainPlanService plan.Service` 作为参数。
 | 
				
			||||||
 | 
					    *   将 `planRepo` 和 `analysisPlanTaskManager` 的注入替换为 `domainPlanService`。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    func NewPlanService(
 | 
				
			||||||
 | 
					    	logger *logs.Logger,
 | 
				
			||||||
 | 
					    	// planRepo repository.PlanRepository, // 移除
 | 
				
			||||||
 | 
					    	domainPlanService plan.Service, // 接收领域层服务
 | 
				
			||||||
 | 
					    	// analysisPlanTaskManager plan.AnalysisPlanTaskManager, // 移除
 | 
				
			||||||
 | 
					    ) PlanService {
 | 
				
			||||||
 | 
					    	return &planService{
 | 
				
			||||||
 | 
					    		logger:            logger,
 | 
				
			||||||
 | 
					    		domainPlanService: domainPlanService, // 注入领域层服务
 | 
				
			||||||
 | 
					    	}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3.  **修改 `PlanService` 接口**:
 | 
				
			||||||
 | 
					    *   接口定义保持不变,仍然接收和返回 DTO。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.  **修改 `planService` 接口实现**:
 | 
				
			||||||
 | 
					    *   **`CreatePlan`**:
 | 
				
			||||||
 | 
					        *   接收 `dto.CreatePlanRequest`。
 | 
				
			||||||
 | 
					        *   使用 `dto.NewPlanFromCreateRequest` 将 DTO 转换为 `*models.Plan`。**注意:此时 `NewPlanFromCreateRequest` 不再包含 `ValidateExecutionOrder()` 和 `ReorderSteps()` 的调用。**
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.CreatePlan(*models.Plan)`。
 | 
				
			||||||
 | 
					        *   将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
 | 
				
			||||||
 | 
					    *   **`GetPlanByID`**:
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.GetPlanByID(id)`。
 | 
				
			||||||
 | 
					        *   将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
 | 
				
			||||||
 | 
					    *   **`ListPlans`**:
 | 
				
			||||||
 | 
					        *   将 `dto.ListPlansQuery` 转换为 `repository.ListPlansOptions`。
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.ListPlans(...)`。
 | 
				
			||||||
 | 
					        *   将返回的 `[]models.Plan` 转换为 `[]dto.PlanResponse`。
 | 
				
			||||||
 | 
					    *   **`UpdatePlan`**:
 | 
				
			||||||
 | 
					        *   使用 `dto.NewPlanFromUpdateRequest` 将 `dto.UpdatePlanRequest` 转换为 `*models.Plan`。**注意:此时 `NewPlanFromUpdateRequest` 不再包含 `ValidateExecutionOrder()` 和 `ReorderSteps()` 的调用。**
 | 
				
			||||||
 | 
					        *   设置 `plan.ID = id`。
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.UpdatePlan(*models.Plan)`。
 | 
				
			||||||
 | 
					        *   将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
 | 
				
			||||||
 | 
					    *   **`DeletePlan`**:
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.DeletePlan(id)`。
 | 
				
			||||||
 | 
					    *   **`StartPlan`**:
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.StartPlan(id)`。
 | 
				
			||||||
 | 
					    *   **`StopPlan`**:
 | 
				
			||||||
 | 
					        *   调用 `s.domainPlanService.StopPlan(id)`。
 | 
				
			||||||
 | 
					    *   **错误处理**: 应用服务层将捕获领域层返回的错误,并可能将其转换为更适合应用服务层或表示层的错误信息(例如,将领域层的 `ErrPlanNotFound` 转换为 `app/service` 层定义的 `ErrPlanNotFound`)。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 第三步:修改 `internal/app/dto/plan_converter.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1.  **移除 `NewPlanFromCreateRequest` 和 `NewPlanFromUpdateRequest` 中的领域逻辑**:
 | 
				
			||||||
 | 
					    *   从 `NewPlanFromCreateRequest` 和 `NewPlanFromUpdateRequest` 函数中移除 `plan.ValidateExecutionOrder()` 和 `plan.ReorderSteps()` 的调用。这些逻辑应该由领域服务来处理。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 第四步:更新 `main.go` 或其他依赖注入点
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   调整 `NewPlanService` 的调用,确保正确注入 `domain/plan/Service` 的实现。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 风险与注意事项:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **事务管理**: 如果领域层的方法需要事务,确保事务在领域层内部或由应用服务层协调。
 | 
				
			||||||
 | 
					*   **错误映射**: 仔细处理领域层错误到应用服务层错误的映射,确保对外暴露的错误信息是恰当的。
 | 
				
			||||||
 | 
					*   **循环依赖**: 确保 `domain` 层不依赖 `app` 层,`app` 层可以依赖 `domain` 层。
 | 
				
			||||||
 | 
					*   **测试**: 重构后需要对所有相关功能进行全面的单元测试和集成测试。
 | 
				
			||||||
@@ -77,9 +77,6 @@ type PlanRepository interface {
 | 
				
			|||||||
	// FindPlansWithPendingTasks 查找所有正在执行的计划
 | 
						// FindPlansWithPendingTasks 查找所有正在执行的计划
 | 
				
			||||||
	FindPlansWithPendingTasks() ([]*models.Plan, error)
 | 
						FindPlansWithPendingTasks() ([]*models.Plan, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// DB 返回底层的数据库连接实例,用于服务层事务
 | 
					 | 
				
			||||||
	DB() *gorm.DB
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志
 | 
						// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志
 | 
				
			||||||
	StopPlanTransactionally(planID uint) error
 | 
						StopPlanTransactionally(planID uint) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -715,11 +712,6 @@ func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error)
 | 
				
			|||||||
	return plans, err
 | 
						return plans, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DB 返回底层的数据库连接实例
 | 
					 | 
				
			||||||
func (r *gormPlanRepository) DB() *gorm.DB {
 | 
					 | 
				
			||||||
	return r.db
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志。
 | 
					// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志。
 | 
				
			||||||
func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
 | 
					func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
 | 
				
			||||||
	return r.db.Transaction(func(tx *gorm.DB) error {
 | 
						return r.db.Transaction(func(tx *gorm.DB) error {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,10 @@ RELAY_API.md
 | 
				
			|||||||
TODO-List.txt
 | 
					TODO-List.txt
 | 
				
			||||||
config.example.yml
 | 
					config.example.yml
 | 
				
			||||||
config.yml
 | 
					config.yml
 | 
				
			||||||
 | 
					design/verification-before-device-deletion/add_get_device_id_configs_to_task.md
 | 
				
			||||||
design/verification-before-device-deletion/index.md
 | 
					design/verification-before-device-deletion/index.md
 | 
				
			||||||
design/verification-before-device-deletion/plan_service_refactor.md
 | 
					design/verification-before-device-deletion/plan_service_refactor.md
 | 
				
			||||||
 | 
					design/verification-before-device-deletion/plan_service_refactor_to_domain.md
 | 
				
			||||||
docs/docs.go
 | 
					docs/docs.go
 | 
				
			||||||
docs/swagger.json
 | 
					docs/swagger.json
 | 
				
			||||||
docs/swagger.yaml
 | 
					docs/swagger.yaml
 | 
				
			||||||
@@ -72,6 +74,7 @@ internal/domain/pig/pig_sick_manager.go
 | 
				
			|||||||
internal/domain/pig/pig_trade_manager.go
 | 
					internal/domain/pig/pig_trade_manager.go
 | 
				
			||||||
internal/domain/plan/analysis_plan_task_manager.go
 | 
					internal/domain/plan/analysis_plan_task_manager.go
 | 
				
			||||||
internal/domain/plan/plan_execution_manager.go
 | 
					internal/domain/plan/plan_execution_manager.go
 | 
				
			||||||
 | 
					internal/domain/plan/plan_service.go
 | 
				
			||||||
internal/domain/plan/task.go
 | 
					internal/domain/plan/task.go
 | 
				
			||||||
internal/domain/task/delay_task.go
 | 
					internal/domain/task/delay_task.go
 | 
				
			||||||
internal/domain/task/full_collection_task.go
 | 
					internal/domain/task/full_collection_task.go
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user