删除设备时检查
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# 方案:删除设备前的使用校验
|
||||
|
||||
## 1. 目标
|
||||
|
||||
在删除设备前,检查该设备是否被任何任务关联。如果设备正在被使用,则禁止删除,并向用户返回明确的错误提示。
|
||||
|
||||
## 2. 核心思路
|
||||
|
||||
我们将遵循您项目清晰的分层架构,将“检查设备是否被任务使用”这一业务规则放在 **应用层** (`internal/app/service/`)
|
||||
中进行协调。当上层请求删除设备时,应用服务会先调用仓库层查询 `device_tasks` 关联表,如果发现设备仍被任务关联,则会拒绝删除并返回一个明确的业务错误。
|
||||
|
||||
## 3. 实施步骤
|
||||
|
||||
### 3.1. 仓库层 (`DeviceRepository`)
|
||||
|
||||
- **动作**: 在 `internal/infra/repository/device_repository.go` 的 `DeviceRepository` 接口中,增加一个新方法
|
||||
`IsDeviceInUse(deviceID uint) (bool, error)`。
|
||||
- **实现**: 在 `gormDeviceRepository` 中实现此方法。该方法将通过对 `models.DeviceTask` 模型执行 `Count`
|
||||
操作来高效地判断是否存在 `device_id` 匹配的记录。这比查询完整记录性能更好。
|
||||
|
||||
### 3.2. 应用层 (`DeviceService`)
|
||||
|
||||
- **动作**:
|
||||
1. 在 `internal/app/service/device_service.go` 文件顶部定义一个新的错误变量 `ErrDeviceInUse`,例如
|
||||
`var ErrDeviceInUse = errors.New("设备正在被一个或多个任务使用,无法删除")`。
|
||||
2. 修改该文件中的 `DeleteDevice` 方法。
|
||||
- **实现**: 在 `DeleteDevice` 方法中,在调用 `s.deviceRepo.Delete()` 之前,先调用我们刚刚创建的
|
||||
`s.deviceRepo.IsDeviceInUse()` 方法。如果返回 `true`,则立即返回 `ErrDeviceInUse` 错误,中断删除流程。
|
||||
|
||||
### 3.3. 表现层 (`DeviceController`)
|
||||
|
||||
- **动作**: 修改 `internal/app/controller/device/device_controller.go` 中的 `DeleteDevice` 方法。
|
||||
- **实现**: 在错误处理逻辑中,增加一个 `case` 来专门捕获从服务层返回的 `service.ErrDeviceInUse`
|
||||
错误。当捕获到此错误时,返回一个带有明确提示信息(如“设备正在被任务使用,无法删除”)和合适 HTTP 状态码(例如 `409 Conflict`)的错误响应。
|
||||
|
||||
## 4. 方案优势
|
||||
|
||||
- **职责清晰**: 业务流程的编排和校验逻辑被正确地放置在应用层,符合您项目清晰的分层架构。
|
||||
- **高效查询**: 通过 `COUNT` 查询代替 `Find`,避免了不必要的数据加载,性能更佳。
|
||||
- **代码内聚**: 与设备相关的数据库操作都统一封装在 `DeviceRepository` 中。
|
||||
- **用户友好**: 通过在控制器层处理特定业务错误,可以给前端返回明确、可操作的错误信息。
|
||||
@@ -19,5 +19,6 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/50
|
||||
3. [现有计划管理逻辑迁移](./plan_service_refactor_to_domain.md)
|
||||
4. [增加设备任务关联表](./device_task_many_to_many_design.md)
|
||||
5. [增加任务增删改查时对设备任务关联表的维护](./device_task_association_maintenance.md)
|
||||
6. [删除设备时检查](./check_before_device_deletion.md)
|
||||
6. [删除设备模板时检查]()
|
||||
7. [删除区域主控时检查]()
|
||||
@@ -154,12 +154,18 @@ func (c *Controller) DeleteDevice(ctx echo.Context) error {
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
if err := c.deviceService.DeleteDevice(deviceID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
case errors.Is(err, service.ErrDeviceInUse):
|
||||
c.logger.Warnf("%s: 尝试删除正在被使用的设备, ID: %s", actionType, deviceID)
|
||||
// 返回 409 Conflict 状态码,表示请求与服务器当前状态冲突
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "设备正在被使用", deviceID)
|
||||
default:
|
||||
c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "服务层删除失败", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "服务层删除失败", deviceID)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备删除成功, ID: %s", actionType, deviceID)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
@@ -11,6 +12,9 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
)
|
||||
|
||||
// ErrDeviceInUse 表示设备正在被任务使用,无法删除
|
||||
var ErrDeviceInUse = errors.New("设备正在被一个或多个任务使用,无法删除")
|
||||
|
||||
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
|
||||
type DeviceService interface {
|
||||
CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error)
|
||||
@@ -142,14 +146,27 @@ func (s *deviceService) DeleteDevice(id string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deviceID := uint(idUint)
|
||||
|
||||
// Check if device exists before deleting
|
||||
_, err = s.deviceRepo.FindByID(uint(idUint))
|
||||
// 检查设备是否存在
|
||||
_, err = s.deviceRepo.FindByID(deviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
return err // 如果未找到,会返回 gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
return s.deviceRepo.Delete(uint(idUint))
|
||||
// 在删除前检查设备是否被任务使用
|
||||
inUse, err := s.deviceRepo.IsDeviceInUse(deviceID)
|
||||
if err != nil {
|
||||
// 如果检查过程中发生数据库错误,则返回错误
|
||||
return fmt.Errorf("检查设备使用情况失败: %w", err)
|
||||
}
|
||||
if inUse {
|
||||
// 如果设备正在被使用,则返回特定的业务错误
|
||||
return ErrDeviceInUse
|
||||
}
|
||||
|
||||
// 只有在未被使用时,才执行删除操作
|
||||
return s.deviceRepo.Delete(deviceID)
|
||||
}
|
||||
|
||||
func (s *deviceService) ManualControl(id string, req *dto.ManualControlDeviceRequest) error {
|
||||
|
||||
@@ -43,6 +43,9 @@ type DeviceRepository interface {
|
||||
|
||||
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
|
||||
GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error)
|
||||
|
||||
// IsDeviceInUse 检查设备是否被任何任务使用
|
||||
IsDeviceInUse(deviceID uint) (bool, error)
|
||||
}
|
||||
|
||||
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
|
||||
@@ -161,3 +164,14 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(areaContro
|
||||
}
|
||||
return &device, nil
|
||||
}
|
||||
|
||||
// IsDeviceInUse 检查设备是否被任何任务使用
|
||||
func (r *gormDeviceRepository) IsDeviceInUse(deviceID uint) (bool, error) {
|
||||
var count int64
|
||||
// 直接对 device_tasks 关联表进行 COUNT 操作,性能最高
|
||||
err := r.db.Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user