跳转至

后台任务(Runner)

Runner 用于在后台执行周期性或长时间运行的任务,如定时清理、数据同步、邮件发送等。

创建 Runner

嵌入 core.IRunner 接口:

package runner

import (
    "context"
    "time"
    "github.com/chuccp/go-web-frame/core"
)

type CleanupRunner struct {
    core.IRunner
    ctx *core.Context
}

func (r *CleanupRunner) Init(c *core.Context) error {
    r.ctx = c
    return nil
}

// Run 方法无参数,返回 error
func (r *CleanupRunner) Run() error {
    ticker := time.NewTicker(30 * time.Minute)
    defer ticker.Stop()

    for {
        select {
        case <-r.ctx.Done():
            // 优雅退出:处理剩余任务
            return nil
        case <-ticker.C:
            // 执行清理任务
            r.cleanup()
        }
    }
}

func (r *CleanupRunner) cleanup() {
    // 清理逻辑
}

注册 Runner

main.go 中注册:

builder := wf.NewBuilder(cfg)
builder.Runner(&runner.CleanupRunner{})
app := builder.Build()
app.Start()

使用定时任务调度

框架内置 component/schedule 组件,支持 cron 表达式调度:

package runner

import (
    "github.com/chuccp/go-web-frame/component/schedule"
    "github.com/chuccp/go-web-frame/core"
    "github.com/chuccp/go-web-frame/log"
    "go.uber.org/zap"
)

type ScheduledRunner struct {
    schedule *schedule.Schedule
    ctx      *core.Context
}

func (r *ScheduledRunner) Init(c *core.Context) error {
    r.ctx = c
    r.schedule = core.GetRunner[*schedule.Schedule](c)
    return nil
}

func (r *ScheduledRunner) Run() error {
    // 添加定时任务(cron 表达式)
    _, err := r.schedule.AddFunc("0 */5 * * * ?", func(c *core.Context) {
        log.Info("running scheduled task", zap.String("name", "data-sync"))
        r.syncData()
    })
    return err
}

func (r *ScheduledRunner) syncData() {
    // 数据同步逻辑
}

注册方式:

builder := wf.NewBuilder(cfg)
// 先注册调度组件,再注册自定义 Runner
builder.Runner(schedule.NewScheduleWithSeconds(), &runner.ScheduledRunner{})
app := builder.Build()
app.Start()

在服务中获取组件

Runner 的 Init 方法中可以获取其他服务、模型和组件:

type EmailRunner struct {
    core.IRunner
    emailService *service.EmailService
    ctx          *core.Context
}

func (r *EmailRunner) Init(c *core.Context) error {
    r.ctx = c
    r.emailService = core.GetService[*service.EmailService](c)
    return nil
}

func (r *EmailRunner) Run() error {
    ticker := time.NewTicker(time.Minute)
    defer ticker.Stop()

    for {
        select {
        case <-r.ctx.Done():
            return nil
        case <-ticker.C:
            r.emailService.SendPendingEmails()
        }
    }
}

完整示例

package main

import (
    "sync"
    "time"

    wf "github.com/chuccp/go-web-frame"
    "github.com/chuccp/go-web-frame/config"
    "github.com/chuccp/go-web-frame/core"
    "github.com/chuccp/go-web-frame/log"
    atomic2 "go.uber.org/atomic"
    "go.uber.org/zap"
)

type DataSyncRunner struct {
    core.IRunner
    ctx     *core.Context
    mu      sync.Mutex
    isRunning *atomic2.Bool
}

func (r *DataSyncRunner) Init(c *core.Context) error {
    r.ctx = c
    r.isRunning = atomic2.NewBool(false)
    return nil
}

func (r *DataSyncRunner) Run() error {
    ticker := time.NewTicker(5 * time.Minute)
    defer ticker.Stop()

    r.syncData() // 立即执行一次

    for {
        select {
        case <-r.ctx.Done():
            log.Info("data sync runner stopping")
            return nil
        case <-ticker.C:
            r.syncData()
        }
    }
}

func (r *DataSyncRunner) syncData() {
    // 防止并发执行
    if !r.isRunning.CompareAndSwap(false, true) {
        return
    }
    defer r.isRunning.Swap(false)

    r.mu.Lock()
    defer r.mu.Unlock()

    log.Info("data sync started")
    // 执行数据同步
    log.Info("data sync completed")
}

func main() {
    cfg, err := config.LoadSingleFileConfig("application.yml")
    if err != nil {
        log.Panic("加载配置失败", zap.Error(err))
    }

    builder := wf.NewBuilder(cfg)
    builder.Runner(&DataSyncRunner{})
    app := builder.Build()
    app.Start()
}

下一步