70 lines
1.9 KiB
Go
70 lines
1.9 KiB
Go
// Package reconcile 是 pay v2 的后台守护/对账装配层:把多个幂等、崩溃安全、
|
|
// 可重跑的周期任务(订单过期清理 / webhook 死信硬化 / crypto 预留冷启动 /
|
|
// 用量刷新 / 查单对账 / 链上孤儿发现)挂到统一 Runner。它处于 main 之下的装配层,
|
|
// 可依赖 store/gateway/accounts/provider;gateway/provider 核心不反向依赖它。
|
|
package reconcile
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
// Task 一个周期任务:名字 + 间隔 + 幂等可重跑的 Run。
|
|
type Task struct {
|
|
Name string
|
|
Interval time.Duration
|
|
Run func(ctx context.Context) error
|
|
}
|
|
|
|
// Runner 持有一组周期任务,逐个跑(RunOnce)或各自 ticker 常驻(Start)。
|
|
type Runner struct {
|
|
tasks []Task
|
|
logf func(format string, args ...any)
|
|
}
|
|
|
|
func NewRunner() *Runner { return &Runner{logf: log.Printf} }
|
|
|
|
// Add 注册一个周期任务。
|
|
func (r *Runner) Add(name string, interval time.Duration, run func(ctx context.Context) error) {
|
|
r.tasks = append(r.tasks, Task{Name: name, Interval: interval, Run: run})
|
|
}
|
|
|
|
// exec 跑单个任务一次:panic recover + error 记录,绝不外抛(单任务失败不拖垮其它)。
|
|
func (r *Runner) exec(ctx context.Context, t Task) {
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.logf("[reconcile] 任务 %s panic 已恢复: %v", t.Name, rec)
|
|
}
|
|
}()
|
|
if err := t.Run(ctx); err != nil {
|
|
r.logf("[reconcile] 任务 %s: %v", t.Name, err)
|
|
}
|
|
}
|
|
|
|
// RunOnce 顺序跑一遍全部任务(启动预热 + 单测入口)。
|
|
func (r *Runner) RunOnce(ctx context.Context) {
|
|
for _, t := range r.tasks {
|
|
r.exec(ctx, t)
|
|
}
|
|
}
|
|
|
|
// Start 每任务一 goroutine + 独立 ticker 常驻;ctx 取消即退出。每 tick 崩溃安全。
|
|
func (r *Runner) Start(ctx context.Context) {
|
|
for _, t := range r.tasks {
|
|
t := t
|
|
go func() {
|
|
tk := time.NewTicker(t.Interval)
|
|
defer tk.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-tk.C:
|
|
r.exec(ctx, t)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|