Gin实践 连载十一 Cron定时任务

Cron定时任务

项目地址:https://github.com/EDDYCJY/go...

如果对你有所帮助,欢迎点个 Star 或赞 ?

在实际的应用项目中,定时任务的使用是很常见的。你是否有过 Golang 如何做定时任务的疑问,莫非是轮询?

在本文中我们将结合我们的项目讲述 Cron

介绍

我们将使用 cron 这个包,它实现了 cron 规范解析器和任务运行器,简单来讲就是包含了定时任务所需的功能

Cron 表达式格式

字段名是否必填允许的值允许的特殊字符
秒(Seconds)Yes0-59* / , -
分(Minutes)Yes0-59* / , -
时(Hours)Yes0-23* / , -
一个月中的某天(Day of month)Yes1-31* / , - ?
月(Month)Yes1-12 or JAN-DEC* / , -
星期几(Day of week)Yes0-6 or SUN-SAT* / , - ?

Cron表达式表示一组时间,使用 6 个空格分隔的字段

可以留意到 Golang 的 Cron 比 Crontab 多了一个秒级,以后遇到秒级要求的时候就省事了

Cron 特殊字符

1、星号 ( * )

星号表示将匹配字段的所有值

2、斜线 ( / )

斜线用户 描述范围的增量,表现为 “N-MAX/x”,first-last/x 的形式,例如 3-59/15 表示此时的第三分钟和此后的每 15 分钟,到59分钟为止。即从 N 开始,使用增量直到该特定范围结束。它不会重复

3、逗号 ( , )

逗号用于分隔列表中的项目。例如,在 Day of week 使用“MON,WED,FRI”将意味着星期一,星期三和星期五

4、连字符 ( - )

连字符用于定义范围。例如,9 - 17 表示从上午 9 点到下午 5 点的每个小时

5、问号 ( ? )

不指定值,用于代替 “ * ”,类似 “ _ ” 的存在,不难理解

预定义的 Cron 时间表

输入简述相当于
@yearly (or @annually)1月1日午夜运行一次0 0 0 1 1 *
@monthly每个月的午夜,每个月的第一个月运行一次0 0 0 1
@weekly每周一次,周日午夜运行一次0 0 0 0
@daily (or @midnight)每天午夜运行一次0 0 0 *
@hourly每小时运行一次0 0

安装

$ go get -u github.com/robfig/cron

实践

在上一章节 Gin实践 连载十 定制 GORM Callbacks 中,我们使用了 GORM 的回调实现了软删除,同时也引入了另外一个问题

就是我怎么硬删除,我什么时候硬删除?这个往往与业务场景有关系,大致为

  • 另外有一套硬删除接口
  • 定时任务清理(或转移、backup)无效数据

在这里我们选用第二种解决方案来进行实践

编写硬删除代码

打开 models 目录下的 tag.go、article.go文件,分别添加以下代码

1、tag.go

func CleanAllTag() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Tag{})

    return true
}

2、article.go

func CleanAllArticle() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Article{})

    return true
}

注意硬删除要使用 Unscoped(),这是 GORM 的约定

编写Cron

在 项目根目录下新建 cron.go 文件,用于编写定时任务的代码,写入文件内容

package main

import (
    "time"
    "log"

    "github.com/robfig/cron"

    "github.com/EDDYCJY/go-gin-example/models"
)

func main() {
    log.Println("Starting...")

    c := cron.New()
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllTag...")
        models.CleanAllTag()
    })
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllArticle...")
        models.CleanAllArticle()
    })

    c.Start()

    t1 := time.NewTimer(time.Second * 10)
    for {
        select {
        case <-t1.C:
            t1.Reset(time.Second * 10)
        }
    }
}

在这段程序中,我们做了如下的事情

1、cron.New()

会根据本地时间创建一个新(空白)的 Cron job runner

func New() *Cron {
    return NewWithLocation(time.Now().Location())
}

// NewWithLocation returns a new Cron job runner.
func NewWithLocation(location *time.Location) *Cron {
    return &Cron{
        entries:  nil,
        add:      make(chan *Entry),
        stop:     make(chan struct{}),
        snapshot: make(chan []*Entry),
        running:  false,
        ErrorLog: nil,
        location: location,
    }
}

2、c.AddFunc()

AddFunc 会向 Cron job runner 添加一个 func ,以按给定的时间表运行

func (c *Cron) AddJob(spec string, cmd Job) error {
    schedule, err := Parse(spec)
    if err != nil {
        return err
    }
    c.Schedule(schedule, cmd)
    return nil
}

会首先解析时间表,如果填写有问题会直接 err,无误则将 func 添加到 Schedule 队列中等待执行

func (c *Cron) Schedule(schedule Schedule, cmd Job) {
    entry := &Entry{
        Schedule: schedule,
        Job:      cmd,
    }
    if !c.running {
        c.entries = append(c.entries, entry)
        return
    }

    c.add <- entry
}

3、c.Start()

在当前执行的程序中启动 Cron 调度程序。其实这里的主体是 goroutine + for + select + timer 的调度控制哦

func (c *Cron) Run() {
    if c.running {
        return
    }
    c.running = true
    c.run()
}

4、time.NewTimer + for + select + t1.Reset

如果你是初学者,大概会有疑问,这是干嘛用的?

(1)time.NewTimer

会创建一个新的定时器,持续你设定的时间 d 后发送一个 channel 消息

(2)for + select

阻塞 select 等待 channel

(3)t1.Reset

会重置定时器,让它重新开始计时
(注意,本文适用于 “t.C已经取走,可直接使用 Reset”)


总的来说,这段程序是为了阻塞主程序而编写的,希望你带着疑问来想,有没有别的办法呢?

有的,你直接 select{} 也可以完成这个需求 :)

验证

$ go run cron.go 
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:56
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:57
2018/04/29 17:03:34 [info] replacing callback `gorm:delete` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:58
2018/04/29 17:03:34 Starting...
2018/04/29 17:03:35 Run models.CleanAllArticle...
2018/04/29 17:03:35 Run models.CleanAllTag...
2018/04/29 17:03:36 Run models.CleanAllArticle...
2018/04/29 17:03:36 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllArticle...

检查输出日志正常,模拟已软删除的数据,定时任务工作OK

小结

定时任务很常见,希望你通过本文能够熟知 Golang 怎么实现一个简单的定时任务调度管理

可以不依赖系统的 Crontab 设置,指不定哪一天就用上了呢

参考

本系列示例代码

本系列目录

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是在 Gin Vue Admin 中创建定时任务的基本步骤: 1. 在后端(Gin)中定义定时任务的接口路由 ```go // 定义定时任务接口路由 router.POST("/api/v1/tasks", task.CreateTask) ``` 2. 在后端控制器中实现创建定时任务的逻辑 ```go func CreateTask(c *gin.Context) { // 从请求中获取定时任务的信息 var task models.Task if err := c.ShouldBindJSON(&task); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 写入数据库,创建定时任务 db.Create(&task) // 返回创建成功的信息 c.JSON(http.StatusOK, gin.H{"status": "success"}) } ``` 3. 在前端(Vue)中实现创建定时任务的界面和逻辑 ```vue <template> <div> <h2>Create Task</h2> <form @submit.prevent="createTask"> <div> <label for="name">Name:</label> <input type="text" id="name" v-model="task.name" required> </div> <div> <label for="cron">Cron:</label> <input type="text" id="cron" v-model="task.cron" required> </div> <div> <button type="submit">Create</button> </div> </form> </div> </template> <script> import axios from 'axios' export default { data() { return { task: { name: '', cron: '', }, } }, methods: { createTask() { axios.post('/api/v1/tasks', this.task) .then(response => { console.log(response.data) alert('Task created successfully') }) .catch(error => { console.log(error) alert('Failed to create task') }) }, }, } </script> ``` 4. 在前端(Vue)中实现定时任务列表的界面和逻辑 ```vue <template> <div> <h2>Task List</h2> <table> <thead> <tr> <th>Name</th> <th>Cron</th> <th>Actions</th> </tr> </thead> <tbody> <tr v-for="task in tasks" :key="task.id"> <td>{{ task.name }}</td> <td>{{ task.cron }}</td> <td> <button @click="runTask(task.id)">Run</button> <button @click="deleteTask(task.id)">Delete</button> </td> </tr> </tbody> </table> </div> </template> <script> import axios from 'axios' export default { data() { return { tasks: [], } }, mounted() { this.getTasks() }, methods: { getTasks() { axios.get('/api/v1/tasks') .then(response => { console.log(response.data) this.tasks = response.data }) .catch(error => { console.log(error) alert('Failed to get tasks') }) }, runTask(id) { axios.post(`/api/v1/tasks/${id}/run`) .then(response => { console.log(response.data) alert('Task executed successfully') }) .catch(error => { console.log(error) alert('Failed to execute task') }) }, deleteTask(id) { axios.delete(`/api/v1/tasks/${id}`) .then(response => { console.log(response.data) this.getTasks() }) .catch(error => { console.log(error) alert('Failed to delete task') }) }, }, } </script> ``` 5. 在后端控制器中实现获取定时任务列表、运行定时任务和删除定时任务的逻辑 ```go func GetTasks(c *gin.Context) { // 从数据库中获取所有的定时任务 var tasks []models.Task db.Find(&tasks) // 返回定时任务列表 c.JSON(http.StatusOK, tasks) } func RunTask(c *gin.Context) { // 获取定时任务的 ID id := c.Param("id") // 根据 ID 从数据库中获取定时任务 var task models.Task db.First(&task, id) // 执行定时任务 // ... // 返回执行成功的信息 c.JSON(http.StatusOK, gin.H{"status": "success"}) } func DeleteTask(c *gin.Context) { // 获取定时任务的 ID id := c.Param("id") // 根据 ID 从数据库中删除定时任务 db.Delete(&models.Task{}, id) // 返回删除成功的信息 c.JSON(http.StatusOK, gin.H{"status": "success"}) } ``` 注意:定时任务的具体实现需要根据业务逻辑进行编写,此处仅为示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值