From 9c3f2a6939aefccdb8c1205af98f4b8593bdd2b6 Mon Sep 17 00:00:00 2001 From: xuans Date: Wed, 30 Sep 2026 10:56:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=BB=E5=8A=A1=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=B0=9D=E8=AF=95=E8=8C=83=E5=9B=B4=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/repository/task_repository.go | 20 +++++------ .../repository/task_repository_test.go | 36 +++++++++++++++++++ doc/Scert开发任务清单.md | 11 ++++++ doc/Scert整改任务执行清单.md | 11 ++++++ doc/Scert整改合并优化清单.md | 11 ++++++ doc/基础整改任务规格.md | 11 ++++++ 6 files changed, 90 insertions(+), 10 deletions(-) diff --git a/backend/internal/repository/task_repository.go b/backend/internal/repository/task_repository.go index 106ffbf..4b23384 100644 --- a/backend/internal/repository/task_repository.go +++ b/backend/internal/repository/task_repository.go @@ -361,6 +361,16 @@ func (r *SQLTaskRepository) RecoverInterruptedTasks(ctx context.Context, finishe rollback() return 0, fmt.Errorf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u6570\u91cf\u5931\u8d25\uff1a%w", err) } + if _, err := tx.ExecContext(ctx, r.query(`UPDATE task_attempts + SET status = 'failed', message = ?, finished_at = ? + WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling') + AND task_id IN ( + SELECT id FROM tasks + WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling') + )`), recoveryMessage, finishedAt); err != nil { + rollback() + return 0, fmt.Errorf("恢复中断任务尝试失败:%w", err) + } result, err := tx.ExecContext(ctx, r.query(`UPDATE tasks SET status = 'failed', stage = 'service_restart', message = ?, finished_at = ?, updated_at = ? WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling')`), recoveryMessage, finishedAt, finishedAt) @@ -377,16 +387,6 @@ func (r *SQLTaskRepository) RecoverInterruptedTasks(ctx context.Context, finishe rollback() return 0, fmt.Errorf("\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u6570\u91cf\u4e0e\u4efb\u52a1\u6570\u91cf\u4e0d\u4e00\u81f4\uff1a\u5ba1\u8ba1 %d,\u4efb\u52a1 %d", auditCount, count) } - if _, err := tx.ExecContext(ctx, r.query(`UPDATE task_attempts - SET status = 'failed', message = ?, finished_at = ? - WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling') - AND task_id IN ( - SELECT id FROM tasks - WHERE status = 'failed' AND stage = 'service_restart' AND finished_at = ? - )`), recoveryMessage, finishedAt, finishedAt); err != nil { - rollback() - return 0, fmt.Errorf("\u6062\u590d\u4e2d\u65ad\u4efb\u52a1\u5c1d\u8bd5\u5931\u8d25\uff1a%w", err) - } if err := tx.Commit(); err != nil { return 0, fmt.Errorf("\u63d0\u4ea4\u4e2d\u65ad\u4efb\u52a1\u6062\u590d\u4e8b\u52a1\u5931\u8d25\uff1a%w", err) } diff --git a/backend/internal/repository/task_repository_test.go b/backend/internal/repository/task_repository_test.go index c45f7ef..3bd8733 100644 --- a/backend/internal/repository/task_repository_test.go +++ b/backend/internal/repository/task_repository_test.go @@ -361,6 +361,42 @@ func TestSQLTaskRepositoryRecoversInterruptedTasksAndAttempts(t *testing.T) { } } +func TestSQLTaskRepositoryRecoveryDoesNotTouchHistoricalRestartTaskAttempts(t *testing.T) { + repository, db := newTaskRepositoryTest(t) + defer db.Close() + ctx := context.Background() + finished := time.Date(2026, 8, 30, 10, 0, 0, 0, time.UTC) + + for _, item := range []Task{ + {ID: "task-active-recovery", DomainID: "domain-1", TaskType: "certificate_issue", Status: "running", Stage: "certificate_issue"}, + {ID: "task-historical-restart", DomainID: "domain-2", TaskType: "certificate_deploy", Status: "failed", Stage: "service_restart", Message: "历史恢复失败", FinishedAt: &finished}, + } { + if err := repository.CreateTask(ctx, item); err != nil { + t.Fatalf("创建恢复边界任务失败:%v", err) + } + } + for _, attempt := range []TaskAttempt{ + {ID: "attempt-active-recovery", TaskID: "task-active-recovery", AttemptNumber: 1, Stage: "certificate_issue", Status: "running"}, + {ID: "attempt-historical-restart", TaskID: "task-historical-restart", AttemptNumber: 1, Stage: "certificate_deploy", Status: "running"}, + } { + if err := repository.CreateAttempt(ctx, attempt); err != nil { + t.Fatalf("创建恢复边界任务尝试失败:%v", err) + } + } + + if count, err := repository.RecoverInterruptedTasks(ctx, finished); err != nil || count != 1 { + t.Fatalf("恢复中断任务数量错误:%d %v", count, err) + } + recovered, err := repository.ListAttempts(ctx, "task-active-recovery") + if err != nil || len(recovered) != 1 || recovered[0].Status != "failed" { + t.Fatalf("活动任务尝试未被恢复:%+v %v", recovered, err) + } + historical, err := repository.ListAttempts(ctx, "task-historical-restart") + if err != nil || len(historical) != 1 || historical[0].Status != "running" || historical[0].FinishedAt != nil { + t.Fatalf("历史恢复任务尝试被错误修改:%+v %v", historical, err) + } +} + func TestSQLTaskRepositoryRecoveryAuditFailureRollsBack(t *testing.T) { repository, db := newTaskRepositoryTest(t) defer db.Close() diff --git a/doc/Scert开发任务清单.md b/doc/Scert开发任务清单.md index ac3af8e..52fa05e 100644 --- a/doc/Scert开发任务清单.md +++ b/doc/Scert开发任务清单.md @@ -1,5 +1,16 @@ # Scert 开发任务清单 +## 最新开发进度(2026-09-30,P0-10 崩溃恢复边界修复) + +**总体验收位置继续保持 P0-03:完成,待复审。** 本轮修复服务重启恢复任务尝试的筛选边界,并完成后端本地全量验证;不把本地测试结果写成生产验收结论。 + +- [x] P0-10-09:恢复任务尝试仅绑定本次恢复前处于活动状态的任务,避免历史 `service_restart` 记录误命中。 +- [x] P0-10-09:新增历史任务时间碰撞回归测试,覆盖历史任务及活动任务尝试的差异化结果。 +- [x] 本轮验证:`go test ./...`、`go vet ./...`、`git diff --check` 全部通过。 +- [~] P0-10 的 DNS 验证任务、重试幂等、三数据库实机矩阵和统一复审仍在进行。 +- [ ] 远端生产部署、清库初始化、密码 SSH、浏览器缓存清理后的生产流程、HTTPS/DNS/ACME 和最终生产门禁未完成。 +- [!] 当前不得升级总体验收,仍为 **P0-03:完成,待复审**,没有新增 `[✓]`。 + ## 最新开发进度(2026-09-30,P0-10 批量部署结果持久化) **总体验收位置继续保持 P0-03:完成,待复审。** 本轮已完成代码开发并通过本地自动化回归,未将本地结果写成生产验收结论。 diff --git a/doc/Scert整改任务执行清单.md b/doc/Scert整改任务执行清单.md index 3a49c10..1431e47 100644 --- a/doc/Scert整改任务执行清单.md +++ b/doc/Scert整改任务执行清单.md @@ -1,5 +1,16 @@ # Scert 整改任务执行清单 +## 最新执行记录(2026-09-30,P0-10 崩溃恢复边界修复) + +总体验收继续保持 **P0-03:完成,待复审**。本批登记任务恢复边界修复和本地自动化证据,不替代真实服务器、真实数据库和生产部署验收。 + +- [x] 仓储层:恢复任务尝试时先按恢复前活动任务范围更新,再收敛父任务状态。 +- [x] 回归覆盖:历史 `service_restart` 任务与本次活动任务使用相同完成时间时,历史任务尝试保持原状态。 +- [x] 自动化门禁:后端全量测试、静态检查、差异检查均通过。 +- [~] 继续执行:P0-10 的 DNS 验证、重试幂等、跨数据库实机矩阵和统一复审。 +- [ ] 尚未执行:P0-12、真实三数据库生产数据清理、密码 SSH、远端多服务器部署、HTTPS/DNS/ACME 和最终生产门禁。 +- [!] 状态约束:不标记 `[✓]`,总体验收仍为 **P0-03:完成,待复审**。 + ## 最新执行记录(2026-09-30,P0-10 部署结果查询闭环) 总体验收继续保持 **P0-03:完成,待复审**。本批只登记代码开发和本地回归证据,不替代真实服务器、真实数据库和生产部署验收。 diff --git a/doc/Scert整改合并优化清单.md b/doc/Scert整改合并优化清单.md index 0443131..03d2cbe 100644 --- a/doc/Scert整改合并优化清单.md +++ b/doc/Scert整改合并优化清单.md @@ -1,5 +1,16 @@ # Scert 整改、合并与优化清单 +## 最新进度摘要(2026-09-30,P0-10 崩溃恢复边界修复) + +**总体验收位置仍为 P0-03:完成,待复审。** 本轮继续先开发、后统一测试,修复服务重启恢复任务尝试的历史记录误命中风险;本地验证结果不等同于生产部署验收通过。 + +- [x] 已完成,等待复审:恢复任务尝试改为依据恢复前仍处于活动状态的父任务筛选,不再依赖恢复完成时间反查。 +- [x] 已完成,等待复审:新增历史 `service_restart` 任务时间碰撞回归测试,确认历史活动尝试不会被本次恢复错误修改。 +- [x] 本轮验证:后端 `go test ./...`、`go vet ./...`、`git diff --check` 均通过。 +- [~] P0-10 的 DNS 验证任务、重试幂等、跨数据库实机矩阵和统一复审仍需继续。 +- [ ] 真实生产数据库清理、密码 SSH、远端多服务器部署、HTTPS/DNS/ACME、平台服务和最终生产门禁仍未完成。 +- [!] 不新增 `[✓]` 复审通过项;总体验收继续保持 **P0-03:完成,待复审**。 + ## 最新进度摘要(2026-09-30,P0-10 批量部署结果持久化) **总体验收位置仍为 P0-03:完成,待复审。** 本轮完成批量部署结果的持久化、项目范围查询和 HTTP 读取接口,并完成本地自动化验证;这些结果不等同于生产部署验收通过。 diff --git a/doc/基础整改任务规格.md b/doc/基础整改任务规格.md index c0d09a0..0fb429f 100644 --- a/doc/基础整改任务规格.md +++ b/doc/基础整改任务规格.md @@ -1,5 +1,16 @@ # 基础整改任务规格 +## 最新规格执行进度(2026-09-30,P0-10 崩溃恢复边界修复) + +**总体验收位置仍为 P0-03:完成,待复审。** 本轮补齐服务重启恢复中任务尝试的范围约束,并通过本地全量验证,尚未满足生产验收的实机条件。 + +- [x] 规格实现:恢复任务尝试依据恢复前活动状态的父任务筛选,不使用完成时间和阶段反查历史任务。 +- [x] 规格验证:新增历史恢复任务时间碰撞测试,确认历史任务尝试不被错误收敛。 +- [x] 规格验证:`go test ./...`、`go vet ./...`、`git diff --check` 通过。 +- [~] 规格待续:DNS 验证、重试幂等、真实三数据库和统一复审。 +- [ ] 规格未验收:远端密码 SSH、多服务器生产推送、HTTPS/DNS/ACME、平台服务和最终生产门禁。 +- [!] 不得把本轮局部开发标记为 `[✓]`;总体验收继续为 **P0-03:完成,待复审**。 + ## 最新规格执行进度(2026-09-30,P0-10 部署结果持久化) **总体验收位置仍为 P0-03:完成,待复审。** 本轮将批量部署结果的存储、查询、权限和错误脱敏纳入已完成开发范围,但尚未满足生产验收的实机条件。