补齐服务重启恢复审计并更新进度清单
This commit is contained in:
parent
f538d4c379
commit
6f40851aae
@ -301,9 +301,24 @@ func (r *SQLTaskRepository) RecoverInterruptedTasks(ctx context.Context, finishe
|
||||
rollback := func() {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
recoveryMessage := "\u670d\u52a1\u91cd\u542f\u5bfc\u81f4\u4efb\u52a1\u4e2d\u65ad\uff0c\u8bf7\u91cd\u65b0\u6267\u884c"
|
||||
auditResult, err := tx.ExecContext(ctx, r.query(`INSERT INTO audit_logs
|
||||
(action, resource_type, resource_id, status, message, created_at, scope_type, project_id, task_id, operator_id)
|
||||
SELECT 'task_recovery', 'task', id, 'failed', ?, ?, scope_type, project_id, id, operator_id
|
||||
FROM tasks
|
||||
WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling')`), recoveryMessage, finishedAt)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return 0, fmt.Errorf("\u5199\u5165\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u5931\u8d25\uff1a%w", err)
|
||||
}
|
||||
auditCount, err := auditResult.RowsAffected()
|
||||
if err != nil {
|
||||
rollback()
|
||||
return 0, fmt.Errorf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u6570\u91cf\u5931\u8d25\uff1a%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')`), "\u670d\u52a1\u91cd\u542f\u5bfc\u81f4\u4efb\u52a1\u4e2d\u65ad\uff0c\u8bf7\u91cd\u65b0\u6267\u884c", finishedAt, finishedAt)
|
||||
WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling')`), recoveryMessage, finishedAt, finishedAt)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return 0, fmt.Errorf("\u6062\u590d\u4e2d\u65ad\u4efb\u52a1\u5931\u8d25\uff1a%w", err)
|
||||
@ -313,9 +328,13 @@ func (r *SQLTaskRepository) RecoverInterruptedTasks(ctx context.Context, finishe
|
||||
rollback()
|
||||
return 0, fmt.Errorf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u6570\u91cf\u5931\u8d25\uff1a%w", err)
|
||||
}
|
||||
if auditCount != count {
|
||||
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')`), "\u670d\u52a1\u91cd\u542f\u5bfc\u81f4\u4efb\u52a1\u4e2d\u65ad\uff0c\u8bf7\u91cd\u65b0\u6267\u884c", finishedAt); err != nil {
|
||||
WHERE status IN ('queued', 'running', 'retry_wait', 'cancelling')`), recoveryMessage, finishedAt); err != nil {
|
||||
rollback()
|
||||
return 0, fmt.Errorf("\u6062\u590d\u4e2d\u65ad\u4efb\u52a1\u5c1d\u8bd5\u5931\u8d25\uff1a%w", err)
|
||||
}
|
||||
|
||||
@ -208,6 +208,64 @@ func TestSQLTaskRepositoryRecoversInterruptedTasksAndAttempts(t *testing.T) {
|
||||
if err != nil || len(completedAttempts) != 1 || completedAttempts[0].Status != "success" {
|
||||
t.Fatalf("\u5df2\u5b8c\u6210\u4efb\u52a1\u5c1d\u8bd5\u4e0d\u5e94\u88ab\u6062\u590d\u903b\u8f91\u4fee\u6539\uff1a%+v %v", completedAttempts, err)
|
||||
}
|
||||
var auditCount int
|
||||
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM audit_logs WHERE action = 'task_recovery'`).Scan(&auditCount); err != nil || auditCount != 2 {
|
||||
t.Fatalf("\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u6570\u91cf\u9519\u8bef\uff1a%d %v", auditCount, err)
|
||||
}
|
||||
rows, err := db.QueryContext(ctx, `SELECT resource_id, status, task_id, scope_type, project_id FROM audit_logs WHERE action = 'task_recovery' ORDER BY resource_id`)
|
||||
if err != nil {
|
||||
t.Fatalf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u5931\u8d25\uff1a%v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for _, expectedID := range []string{"task-queued", "task-running"} {
|
||||
if !rows.Next() {
|
||||
t.Fatalf("\u7f3a\u5c11\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\uff1a%s", expectedID)
|
||||
}
|
||||
var resourceID, status, taskID, scopeType, projectID string
|
||||
if err := rows.Scan(&resourceID, &status, &taskID, &scopeType, &projectID); err != nil {
|
||||
t.Fatalf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u5931\u8d25\uff1a%v", err)
|
||||
}
|
||||
if resourceID != expectedID || status != "failed" || taskID != expectedID || scopeType != "project" {
|
||||
t.Fatalf("\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u5185\u5bb9\u9519\u8bef:id=%s status=%s task=%s scope=%s project=%s", resourceID, status, taskID, scopeType, projectID)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
t.Fatalf("\u8bfb\u53d6\u4e2d\u65ad\u4efb\u52a1\u5ba1\u8ba1\u6d41\u5931\u8d25\uff1a%v", err)
|
||||
}
|
||||
count, err = repository.RecoverInterruptedTasks(ctx, finished.Add(time.Minute))
|
||||
if err != nil || count != 0 {
|
||||
t.Fatalf("\u670d\u52a1\u91cd\u542f\u6062\u590d\u4e0d\u5e94\u91cd\u590d\u6267\u884c\uff1a%d %v", count, err)
|
||||
}
|
||||
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM audit_logs WHERE action = 'task_recovery'`).Scan(&auditCount); err != nil || auditCount != 2 {
|
||||
t.Fatalf("\u91cd\u590d\u6062\u590d\u4e0d\u5e94\u65b0\u589e\u5ba1\u8ba1\uff1a%d %v", auditCount, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLTaskRepositoryRecoveryAuditFailureRollsBack(t *testing.T) {
|
||||
repository, db := newTaskRepositoryTest(t)
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
if err := repository.CreateTask(ctx, Task{
|
||||
ID: "task-recovery-audit-failure", DomainID: "domain-1", TaskType: "certificate_issue",
|
||||
Status: "running", Stage: "certificate_issue", Message: "\u6b63\u5728\u6267\u884c",
|
||||
}); err != nil {
|
||||
t.Fatalf("\u521b\u5efa\u5ba1\u8ba1\u56de\u6eda\u6d4b\u8bd5\u4efb\u52a1\u5931\u8d25\uff1a%v", err)
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, `CREATE TRIGGER reject_task_recovery_audit BEFORE INSERT ON audit_logs BEGIN SELECT RAISE(ABORT, 'audit-failure'); END`); err != nil {
|
||||
t.Fatalf("\u521b\u5efa\u5ba1\u8ba1\u5931\u8d25\u89e6\u53d1\u5668\u5931\u8d25\uff1a%v", err)
|
||||
}
|
||||
defer db.ExecContext(context.Background(), `DROP TRIGGER reject_task_recovery_audit`)
|
||||
if count, err := repository.RecoverInterruptedTasks(ctx, time.Date(2026, 8, 30, 10, 0, 0, 0, time.UTC)); err == nil || count != 0 {
|
||||
t.Fatalf("\u5ba1\u8ba1\u5931\u8d25\u65f6\u5e94\u56de\u6eda\u6062\u590d\uff1a%d %v", count, err)
|
||||
}
|
||||
item, err := repository.GetTask(ctx, "task-recovery-audit-failure")
|
||||
if err != nil || item.Status != "running" || item.Stage != "certificate_issue" {
|
||||
t.Fatalf("\u5ba1\u8ba1\u5931\u8d25\u540e\u4efb\u52a1\u72b6\u6001\u672a\u56de\u6eda\uff1a%+v %v", item, err)
|
||||
}
|
||||
var auditCount int
|
||||
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM audit_logs`).Scan(&auditCount); err != nil || auditCount != 0 {
|
||||
t.Fatalf("\u5ba1\u8ba1\u5931\u8d25\u540e\u4e0d\u5e94\u4fdd\u7559\u65e5\u5fd7\uff1a%d %v", auditCount, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLTaskRepositoryRejectsConcurrentDuplicateIdempotencyKeys(t *testing.T) {
|
||||
|
||||
@ -3605,3 +3605,14 @@ Linux Ubuntu 22.04 上的 `Scert-admin` 手动启停、无开机自启动、无
|
||||
- [!] 总体验收不得升级:当前仍为 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
本轮下一步继续审查任务中心剩余入口和备份恢复;开发阶段收尾后,再统一执行生产构建、实机部署和验收。
|
||||
|
||||
## 本轮开发补充(2026-09-29)
|
||||
|
||||
总体验收位置继续保持 **P0-03:完成,待复审**;本轮完成服务重启恢复审计链开发,不代表 P0-10 整项复审或生产部署验收通过。
|
||||
|
||||
- [x] 已完成,等待复审:服务重启恢复会为每个 `queued`、`running`、`retry_wait`、`cancelling` 任务写入 `task_recovery` 失败审计,并保留任务、项目和创建人归属。
|
||||
- [x] 已完成,等待复审:恢复事务新增审计写入失败回滚保护;审计数量与实际恢复任务数量不一致时,任务、尝试和审计全部回滚。
|
||||
- [x] 已完成,等待复审:新增重复恢复与审计失败回滚测试;专项恢复测试、后端全量 `go test ./...` 和 `git diff --check` 均通过。
|
||||
- [~] 进行中:P0-10 的 DNS 验证、批量部署结果、其他入口任务事务链、重试幂等及跨数据库实机验证仍需继续审查。
|
||||
- [ ] 未开始:P0-12 备份恢复、浏览器清缓存、密码 SSH、远端部署和最终生产门禁仍未执行。
|
||||
- [!] 总体验收不得升级:当前仍处于 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
@ -911,3 +911,14 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全
|
||||
- [!] 总体验收不得升级:当前仍为 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
本轮下一步继续审查任务中心剩余入口和备份恢复;开发阶段收尾后,再统一执行生产构建、实机部署和验收。
|
||||
|
||||
## 本轮开发补充(2026-09-29)
|
||||
|
||||
总体验收位置继续保持 **P0-03:完成,待复审**;本轮完成服务重启恢复审计链开发,不代表 P0-10 整项复审或生产部署验收通过。
|
||||
|
||||
- [x] 已完成,等待复审:服务重启恢复会为每个 `queued`、`running`、`retry_wait`、`cancelling` 任务写入 `task_recovery` 失败审计,并保留任务、项目和创建人归属。
|
||||
- [x] 已完成,等待复审:恢复事务新增审计写入失败回滚保护;审计数量与实际恢复任务数量不一致时,任务、尝试和审计全部回滚。
|
||||
- [x] 已完成,等待复审:新增重复恢复与审计失败回滚测试;专项恢复测试、后端全量 `go test ./...` 和 `git diff --check` 均通过。
|
||||
- [~] 进行中:P0-10 的 DNS 验证、批量部署结果、其他入口任务事务链、重试幂等及跨数据库实机验证仍需继续审查。
|
||||
- [ ] 未开始:P0-12 备份恢复、浏览器清缓存、密码 SSH、远端部署和最终生产门禁仍未执行。
|
||||
- [!] 总体验收不得升级:当前仍处于 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
@ -1548,3 +1548,14 @@ P0-11 生产验收门禁
|
||||
- [!] 总体验收不得升级:当前仍为 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
本轮下一步继续审查任务中心剩余入口和备份恢复;开发阶段收尾后,再统一执行生产构建、实机部署和验收。
|
||||
|
||||
## 本轮开发补充(2026-09-29)
|
||||
|
||||
总体验收位置继续保持 **P0-03:完成,待复审**;本轮完成服务重启恢复审计链开发,不代表 P0-10 整项复审或生产部署验收通过。
|
||||
|
||||
- [x] 已完成,等待复审:服务重启恢复会为每个 `queued`、`running`、`retry_wait`、`cancelling` 任务写入 `task_recovery` 失败审计,并保留任务、项目和创建人归属。
|
||||
- [x] 已完成,等待复审:恢复事务新增审计写入失败回滚保护;审计数量与实际恢复任务数量不一致时,任务、尝试和审计全部回滚。
|
||||
- [x] 已完成,等待复审:新增重复恢复与审计失败回滚测试;专项恢复测试、后端全量 `go test ./...` 和 `git diff --check` 均通过。
|
||||
- [~] 进行中:P0-10 的 DNS 验证、批量部署结果、其他入口任务事务链、重试幂等及跨数据库实机验证仍需继续审查。
|
||||
- [ ] 未开始:P0-12 备份恢复、浏览器清缓存、密码 SSH、远端部署和最终生产门禁仍未执行。
|
||||
- [!] 总体验收不得升级:当前仍处于 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
@ -489,3 +489,14 @@ P0-06 与 P0-07 均维持 `[~]`,六阶段中的隔离、生产和最终验收
|
||||
- [!] 总体验收不得升级:当前仍为 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
本轮下一步继续审查任务中心剩余入口和备份恢复;开发阶段收尾后,再统一执行生产构建、实机部署和验收。
|
||||
|
||||
## 本轮开发补充(2026-09-29)
|
||||
|
||||
总体验收位置继续保持 **P0-03:完成,待复审**;本轮完成服务重启恢复审计链开发,不代表 P0-10 整项复审或生产部署验收通过。
|
||||
|
||||
- [x] 已完成,等待复审:服务重启恢复会为每个 `queued`、`running`、`retry_wait`、`cancelling` 任务写入 `task_recovery` 失败审计,并保留任务、项目和创建人归属。
|
||||
- [x] 已完成,等待复审:恢复事务新增审计写入失败回滚保护;审计数量与实际恢复任务数量不一致时,任务、尝试和审计全部回滚。
|
||||
- [x] 已完成,等待复审:新增重复恢复与审计失败回滚测试;专项恢复测试、后端全量 `go test ./...` 和 `git diff --check` 均通过。
|
||||
- [~] 进行中:P0-10 的 DNS 验证、批量部署结果、其他入口任务事务链、重试幂等及跨数据库实机验证仍需继续审查。
|
||||
- [ ] 未开始:P0-12 备份恢复、浏览器清缓存、密码 SSH、远端部署和最终生产门禁仍未执行。
|
||||
- [!] 总体验收不得升级:当前仍处于 **P0-03 完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
Loading…
Reference in New Issue
Block a user