bmad 分析师工作
This commit is contained in:
		
							
								
								
									
										43
									
								
								bmad/bmm-workflow-status.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								bmad/bmm-workflow-status.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					# Workflow Status Template
 | 
				
			||||||
 | 
					# This tracks progress through phases 1-3 of the BMM methodology
 | 
				
			||||||
 | 
					# Phase 4 (Implementation) is tracked separately in sprint-status.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# generated: 2025年11月1日星期六
 | 
				
			||||||
 | 
					# project: pig-farm-controller
 | 
				
			||||||
 | 
					# project_type: software
 | 
				
			||||||
 | 
					# project_level: 0
 | 
				
			||||||
 | 
					# field_type: brownfield
 | 
				
			||||||
 | 
					# workflow_path: brownfield-level-0.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# STATUS DEFINITIONS:
 | 
				
			||||||
 | 
					# ==================
 | 
				
			||||||
 | 
					# Initial Status (before completion):
 | 
				
			||||||
 | 
					#   - required: Must be completed to progress
 | 
				
			||||||
 | 
					#   - optional: Can be completed but not required
 | 
				
			||||||
 | 
					#   - recommended: Strongly suggested but not required
 | 
				
			||||||
 | 
					#   - conditional: Required only if certain conditions met (e.g., if_has_ui)
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Completion Status:
 | 
				
			||||||
 | 
					#   - {file-path}: File created/found (e.g., "docs/product-brief.md")
 | 
				
			||||||
 | 
					#   - skipped: Optional/conditional workflow that was skipped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					generated: "2025年11月1日星期六"
 | 
				
			||||||
 | 
					project: "pig-farm-controller"
 | 
				
			||||||
 | 
					project_type: "software"
 | 
				
			||||||
 | 
					project_level: "0"
 | 
				
			||||||
 | 
					field_type: "brownfield"
 | 
				
			||||||
 | 
					workflow_path: "brownfield-level-0.yaml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					workflow_status: "
 | 
				
			||||||
 | 
					  # Prerequisite: Documentation
 | 
				
			||||||
 | 
					  document-project: bmad/deep-dive-deletion-logic.md
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Phase 1: Analysis
 | 
				
			||||||
 | 
					  brainstorm-project: optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Phase 2: Planning
 | 
				
			||||||
 | 
					  tech-spec: required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Phase 4: Implementation
 | 
				
			||||||
 | 
					  sprint-planning: required
 | 
				
			||||||
 | 
					"
 | 
				
			||||||
@@ -4,6 +4,6 @@
 | 
				
			|||||||
# Date: 2025-11-01T09:44:39.652Z
 | 
					# Date: 2025-11-01T09:44:39.652Z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
user_name: 主人
 | 
					user_name: 主人
 | 
				
			||||||
communication_language: zh-CN
 | 
					communication_language: 中文
 | 
				
			||||||
document_output_language: zh-CN
 | 
					document_output_language: 中文
 | 
				
			||||||
output_folder: '{project-root}/bmad'
 | 
					output_folder: '{project-root}/bmad'
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										248
									
								
								bmad/deep-dive-deletion-logic.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								bmad/deep-dive-deletion-logic.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
				
			|||||||
 | 
					# 深度分析:设备、区域主控和设备模板的删除逻辑
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 概述
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					本文档对 `pig-farm-controller` 项目中设备、区域主控和设备模板的删除逻辑进行了深度分析。重点关注了 API 入口点、控制器层、服务层以及存储库层的实现,并详细描述了为解决 Issue #50(删除时需检测资源是否被使用)而进行的修改。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 1. API 入口点 (router.go)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					API 路由在 `internal/app/api/router.go` 中定义。以下是与删除操作相关的路由:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **删除设备:**
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice)
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    路径: `/api/v1/devices/{id}`
 | 
				
			||||||
 | 
					    处理函数: `a.deviceController.DeleteDevice`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **删除区域主控:**
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController)
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    路径: `/api/v1/area-controllers/{id}`
 | 
				
			||||||
 | 
					    处理函数: `a.deviceController.DeleteAreaController`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **删除设备模板:**
 | 
				
			||||||
 | 
					    ```go
 | 
				
			||||||
 | 
					    deviceTemplateGroup.DELETE("/:id", a.deviceController.DeleteDeviceTemplate)
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					    路径: `/api/v1/device-templates/{id}`
 | 
				
			||||||
 | 
					    处理函数: `a.deviceController.DeleteDeviceTemplate`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					所有这些删除操作都通过 HTTP `DELETE` 方法,并由 `deviceController` 中的相应函数处理。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2. 控制器层 (device_controller.go)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					控制器层位于 `internal/app/controller/device/device_controller.go`。`Controller` 结构体封装了设备、区域主控和设备模板相关的业务逻辑。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteDevice(ctx echo.Context) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   从请求上下文中获取设备 ID。
 | 
				
			||||||
 | 
					*   调用 `c.deviceService.DeleteDevice(deviceID)` 执行删除操作。
 | 
				
			||||||
 | 
					*   处理服务层返回的错误,包括 `gorm.ErrRecordNotFound` 和其他内部错误。
 | 
				
			||||||
 | 
					*   成功时返回成功响应。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteAreaController(ctx echo.Context) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   从请求上下文中获取区域主控 ID。
 | 
				
			||||||
 | 
					*   调用 `c.deviceService.DeleteAreaController(acID)` 执行删除操作。
 | 
				
			||||||
 | 
					*   处理服务层返回的错误。
 | 
				
			||||||
 | 
					*   成功时返回成功响应。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteDeviceTemplate(ctx echo.Context) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   从请求上下文中获取设备模板 ID。
 | 
				
			||||||
 | 
					*   调用 `c.deviceService.DeleteDeviceTemplate(dtID)` 执行删除操作。
 | 
				
			||||||
 | 
					*   处理服务层返回的错误。
 | 
				
			||||||
 | 
					*   成功时返回成功响应。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 3. 服务层 (device_service.go)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					服务层位于 `internal/app/service/device_service.go`。`deviceService` 结构体实现了 `DeviceService` 接口,并依赖于 `deviceRepo`、`areaControllerRepo`、`deviceTemplateRepo` 和 `planRepo`。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteDevice(id string) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改前:**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					func (s *deviceService) DeleteDevice(id string) error {
 | 
				
			||||||
 | 
					    idUint, err := strconv.ParseUint(id, 10, 64)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        return err
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _, err = s.deviceRepo.FindByID(uint(idUint))
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					        return err
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s.deviceRepo.Delete(uint(idUint))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改后 (新增“使用中”检查):**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					**新增 `isDeviceInUseByActivePlan` 辅助方法:**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
 | 
					            var params map[string]interface{}
 | 
				
			||||||
 | 
					            if err := json.Unmarshal(task.Parameters, ¶ms); 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					此方法现在会在删除设备前,检查该设备是否被任何活动计划中的任务所引用。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteAreaController(id string) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改前:**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s.areaControllerRepo.Delete(uint(idUint))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改后 (新增“使用中”检查):**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					此方法现在会在删除区域主控前,检查是否有设备正在使用该区域主控。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `DeleteDeviceTemplate(id string) error`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改前:**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s.deviceTemplateRepo.Delete(uint(idUint))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**修改后 (新增“使用中”检查):**
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					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))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					此方法现在会在删除设备模板前,检查是否有设备正在使用该设备模板。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 4. 存储库层
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `device_repository.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **`FindByDeviceTemplateID(deviceTemplateID uint) ([]*models.Device, error)`**: 用于查找所有使用特定设备模板的设备。
 | 
				
			||||||
 | 
					*   **`ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error)`**: 用于查找所有与特定区域主控关联的设备。
 | 
				
			||||||
 | 
					*   **`Delete(id uint) error`**: 此方法已包含对 `deviceCommandLogRepo` 和 `pendingCollectionRepo` 的检查,确保设备在删除前没有相关的命令日志或待处理采集请求。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `plan_repository.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **`FindPlansWithPendingTasks() ([]*models.Plan, error)`**: 用于查找所有具有待处理任务的活动计划。
 | 
				
			||||||
 | 
					*   **`FlattenPlanTasks(planID uint) ([]models.Task, error)`**: 用于递归展开计划,获取其包含的所有任务。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `internal/infra/models/plan.go`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*   **`Task` 结构体**: `Parameters datatypes.JSON` 字段用于存储任务特定参数,其中可能包含 `device_id`。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 5. 总结与改进
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					通过在服务层添加“使用中”检查,我们确保了在删除设备、区域主控和设备模板时,如果它们正在被其他资源(如设备或活动计划)使用,则会阻止删除操作并返回相应的错误信息。这解决了 Issue #50 中提出的问题,提高了系统的健壮性和数据一致性。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					未来的改进可以包括:
 | 
				
			||||||
 | 
					*   为“使用中”检查提供更详细的错误信息,例如列出正在使用该资源的具体设备或计划。
 | 
				
			||||||
 | 
					*   考虑在删除操作中引入级联删除或强制删除选项(需谨慎)。
 | 
				
			||||||
 | 
					*   为 `Task` 的 `Parameters` 定义更严格的结构体,以便更安全地解析和访问其中的 `device_id`。
 | 
				
			||||||
@@ -24,6 +24,12 @@
 | 
				
			|||||||
- [API Contracts](./api-contracts-main.md)
 | 
					- [API Contracts](./api-contracts-main.md)
 | 
				
			||||||
- [Data Models](./data-models-main.md)
 | 
					- [Data Models](./data-models-main.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Deep-Dive Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Detailed exhaustive analysis of specific areas:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [删除逻辑深度分析](./deep-dive-deletion-logic.md) - 对设备、区域主控和设备模板删除逻辑的全面分析 (文件数: 5, 代码行数: N/A) - 生成日期: 2025年11月1日星期六
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Existing Documentation
 | 
					## Existing Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [README.md](./README.md) - Project README
 | 
					- [README.md](./README.md) - Project README
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,7 @@ type deviceService struct {
 | 
				
			|||||||
	deviceRepo         repository.DeviceRepository
 | 
						deviceRepo         repository.DeviceRepository
 | 
				
			||||||
	areaControllerRepo repository.AreaControllerRepository
 | 
						areaControllerRepo repository.AreaControllerRepository
 | 
				
			||||||
	deviceTemplateRepo repository.DeviceTemplateRepository
 | 
						deviceTemplateRepo repository.DeviceTemplateRepository
 | 
				
			||||||
 | 
						planRepo           repository.PlanRepository // 新增计划仓库依赖
 | 
				
			||||||
	deviceDomainSvc    device.Service            // 依赖领域服务
 | 
						deviceDomainSvc    device.Service            // 依赖领域服务
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,16 +47,48 @@ func NewDeviceService(
 | 
				
			|||||||
	deviceRepo repository.DeviceRepository,
 | 
						deviceRepo repository.DeviceRepository,
 | 
				
			||||||
	areaControllerRepo repository.AreaControllerRepository,
 | 
						areaControllerRepo repository.AreaControllerRepository,
 | 
				
			||||||
	deviceTemplateRepo repository.DeviceTemplateRepository,
 | 
						deviceTemplateRepo repository.DeviceTemplateRepository,
 | 
				
			||||||
 | 
						planRepo repository.PlanRepository,
 | 
				
			||||||
	deviceDomainSvc device.Service,
 | 
						deviceDomainSvc device.Service,
 | 
				
			||||||
) DeviceService {
 | 
					) DeviceService {
 | 
				
			||||||
	return &deviceService{
 | 
						return &deviceService{
 | 
				
			||||||
		deviceRepo:         deviceRepo,
 | 
							deviceRepo:         deviceRepo,
 | 
				
			||||||
		areaControllerRepo: areaControllerRepo,
 | 
							areaControllerRepo: areaControllerRepo,
 | 
				
			||||||
		deviceTemplateRepo: deviceTemplateRepo,
 | 
							deviceTemplateRepo: deviceTemplateRepo,
 | 
				
			||||||
 | 
							planRepo:           planRepo,
 | 
				
			||||||
		deviceDomainSvc:    deviceDomainSvc,
 | 
							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, ¶ms); 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 ---
 | 
					// --- Devices ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) {
 | 
					func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) {
 | 
				
			||||||
@@ -149,6 +182,13 @@ func (s *deviceService) DeleteDevice(id string) error {
 | 
				
			|||||||
		return err
 | 
							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))
 | 
						return s.deviceRepo.Delete(uint(idUint))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -263,6 +303,15 @@ func (s *deviceService) DeleteAreaController(id string) error {
 | 
				
			|||||||
		return err
 | 
							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))
 | 
						return s.areaControllerRepo.Delete(uint(idUint))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -369,5 +418,14 @@ func (s *deviceService) DeleteDeviceTemplate(id string) error {
 | 
				
			|||||||
		return err
 | 
							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))
 | 
						return s.deviceTemplateRepo.Delete(uint(idUint))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package repository
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,11 +46,21 @@ type DeviceRepository interface {
 | 
				
			|||||||
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
 | 
					// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
 | 
				
			||||||
type gormDeviceRepository struct {
 | 
					type gormDeviceRepository struct {
 | 
				
			||||||
	db                    *gorm.DB
 | 
						db                    *gorm.DB
 | 
				
			||||||
 | 
						deviceCommandLogRepo  DeviceCommandLogRepository
 | 
				
			||||||
 | 
						pendingCollectionRepo PendingCollectionRepository
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewGormDeviceRepository 创建一个新的 DeviceRepository GORM 实现实例
 | 
					// NewGormDeviceRepository 创建一个新的 DeviceRepository GORM 实现实例
 | 
				
			||||||
func NewGormDeviceRepository(db *gorm.DB) DeviceRepository {
 | 
					func NewGormDeviceRepository(
 | 
				
			||||||
	return &gormDeviceRepository{db: db}
 | 
						db *gorm.DB,
 | 
				
			||||||
 | 
						deviceCommandLogRepo DeviceCommandLogRepository,
 | 
				
			||||||
 | 
						pendingCollectionRepo PendingCollectionRepository,
 | 
				
			||||||
 | 
					) DeviceRepository {
 | 
				
			||||||
 | 
						return &gormDeviceRepository{
 | 
				
			||||||
 | 
							db:                    db,
 | 
				
			||||||
 | 
							deviceCommandLogRepo:  deviceCommandLogRepo,
 | 
				
			||||||
 | 
							pendingCollectionRepo: pendingCollectionRepo,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create 创建一个新的设备记录
 | 
					// Create 创建一个新的设备记录
 | 
				
			||||||
@@ -129,6 +140,24 @@ func (r *gormDeviceRepository) Update(device *models.Device) error {
 | 
				
			|||||||
// Delete 根据 ID 删除一个设备
 | 
					// Delete 根据 ID 删除一个设备
 | 
				
			||||||
// GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。
 | 
					// GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。
 | 
				
			||||||
func (r *gormDeviceRepository) Delete(id uint) error {
 | 
					func (r *gormDeviceRepository) Delete(id uint) error {
 | 
				
			||||||
 | 
						// 检查是否有相关的设备命令日志
 | 
				
			||||||
 | 
						logs, err := r.deviceCommandLogRepo.FindByDeviceID(id)
 | 
				
			||||||
 | 
						if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return fmt.Errorf("查询设备 %d 的命令日志失败: %w", id, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(logs) > 0 {
 | 
				
			||||||
 | 
							return errors.New("设备有相关的命令日志,不能删除")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 检查是否有相关的待处理采集请求
 | 
				
			||||||
 | 
						pendingCollections, err := r.pendingCollectionRepo.FindByDeviceID(id)
 | 
				
			||||||
 | 
						if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return fmt.Errorf("查询设备 %d 的待处理采集请求失败: %w", id, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(pendingCollections) > 0 {
 | 
				
			||||||
 | 
							return errors.New("设备有相关的待处理采集请求,不能删除")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return r.db.Delete(&models.Device{}, id).Error
 | 
						return r.db.Delete(&models.Device{}, id).Error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user