Files
2025-11-01 16:29:18 +08:00

94 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Why
关键的系统预定义计划(如定时数据采集)应该具备高度的韧性。无论它们之前因为何种原因(例如,执行失败、手动停止、应用异常崩溃)而处于非活动状态,在应用下一次启动时,都应该被自动恢复为“启用”状态,以确保核心功能的持续、可靠运行。
当前的启动逻辑无法将在“失败”或“停止”状态的系统计划自动拉起,需要手动干预,这降低了系统的鲁棒性。
## What Changes
本次变更的核心是对 `PlanRepository` 进行一次重要的重构,以消除 `UpdatePlan` 函数的设计缺陷,并在此基础上,实现系统任务的启动自愈功能。
1. **重构 `PlanRepository`**:
- 将行为不符合名称预期的 `UpdatePlan` 函数重命名为 `UpdatePlanMetadataAndStructure`
- 创建一个全新的、行为正确的 `UpdatePlan` 函数,确保全字段更新。
2. **修正调用点**:
- 更新 `data_initializer.go` 中的调用,使其调用重命名后的 `UpdatePlanMetadataAndStructure`,并辅以 `UpdatePlanStatus` 来实现安全的自愈逻辑。
- 确认 `plan_service.go` 中的调用会自然地指向新的、正确的 `UpdatePlan` 函数。
## Impact
- **Affected Specs**: `design.md` (已合并入本文档) 详细描述了重构的设计与决策。
- **Affected Code**:
- `internal/infra/repository/plan_repository.go`: 接口和实现将被重构。
- `internal/core/data_initializer.go`: 调用点将被修正。
- `internal/app/service/plan_service.go`: 调用点将隐式地指向新实现。
---
## Design Document: `UpdatePlan` Refactoring
### 1. 问题背景与根源分析
在实现“系统任务启动自愈”功能时我们发现了一个深层次的存储库Repository层设计问题。
最初的尝试(在 `data_initializer.go` 中移除状态同步代码)失败了。经过深入调查,根源在于 `plan_repository.go` 中的 `UpdatePlan` 函数实现存在歧义,其行为与函数名不符。
#### `UpdatePlan` 的问题
该函数的内部实现 `reconcilePlanNode` 使用了 GORM 的 `Select(...)` 方法来指定要更新的数据库字段。然而,这个字段列表中**只包含了计划的元数据**(如 `Name`, `Description`, `CronExpression` 等),**并未包含 `Status` 字段**。
```go
// reconcilePlanNode in plan_repository.go
if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil {
return err
}
```
这导致了以下问题:
1. **名不副实**:函数名 `UpdatePlan` 暗示了一个完整的、全字段的更新操作,但其行为却是一个不包含状态(`Status`)和执行计数(`ExecuteCount`)等运行时信息的“部分更新”。
2. **行为不符合预期**:任何调用此函数并期望更新 `Status` 字段的上层业务逻辑,都会静默失败(即代码不报错,但数据未更新),这极易引发难以追踪的 Bug。
3. **潜在风险**:直接修改这个函数以包含 `Status` 字段是危险的。因为我们无法确定项目中其他调用方是否依赖于它“不更新状态”这一隐性行为。任何草率的修改都可能破坏现有功能。
### 2. 重构方案:明确职责,消除歧义
为了从根本上解决此问题,并保证系统的健壮性和可维护性,我们决定对 `PlanRepository` 接口及其实现进行重构。
核心思想是**让函数的名称和行为完全匹配**。
#### 2.1. 接口与实现变更
我们将对 `plan_repository.go` 进行以下修改:
1. **重命名旧函数**:将现有的、有问题的 `UpdatePlan` 函数重命名为 `UpdatePlanMetadataAndStructure`。这个新名字精确地描述了它的实际行为:只更新计划的元数据和结构(任务或子计划),而不触及运行时状态。
2. **创建新函数**:创建一个新的、名为 `UpdatePlan` 的函数。这个新函数将提供一个符合开发者直觉的、真正的“全字段更新”功能。它将使用 GORM 的 `Save` 方法来实现,确保模型对象中的所有字段都被持久化到数据库。
```go
// 新的、正确的 UpdatePlan 实现
func (r *gormPlanRepository) UpdatePlan(plan *models.Plan) error {
return r.db.Transaction(func(tx *gorm.DB) error {
return tx.Save(plan).Error
})
}
```
#### 2.2. 调用点修正
在完成上述重构后,我们需要审查并修正所有原 `UpdatePlan` 的调用点,根据其业务意图,将其指向正确的函数:
1. **`internal/core/data_initializer.go`**:
* **业务意图**:在应用启动时,用预定义模板更新系统计划的元数据,并强制重置其状态为“启用”。
* **修正方案**:此处的调用应该指向 `UpdatePlanMetadataAndStructure`,以确保只更新元数据。后续再通过调用专用的 `UpdatePlanStatus` 方法来安全地重置状态。这完美地分离了两个不同的关注点。
2. **`internal/app/service/plan_service.go`**:
* **业务意图**:提供一个供 API 使用的公共服务,允许用户完整地更新一个计划的所有信息。
* **修正方案**:此处的调用应该指向新的 `UpdatePlan` 函数。由于函数名没有改变,此文件中的代码**无需修改**,但在逻辑上它已经从调用一个有问题的旧函数,变成了调用一个行为正确的新函数。
### 3. 结论
这个重构方案不仅解决了最初的“系统任务自愈”功能实现障碍,更重要的是,它消除了一处严重的设计隐患,提升了代码库的整体质量和可维护性。
通过让函数名副其实,我们为未来的开发和维护工作扫清了障碍,降低了出现类似 Bug 的风险。