Files
hadja_server/pub/common/cronx/cron.go
T
rootandClaude Opus 4.7 79ac89b458 Initial commit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-07 10:47:18 +08:00

149 lines
3.3 KiB
Go

package cronx
import (
"context"
"sync"
"time"
"videoweb/pub/common/log"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
type CronJob interface {
Run() error
Desc() string
Name() string
}
type CronJobAndSpec struct {
CronJob CronJob
Spec string
}
type BaseJob struct {
ctx context.Context
spec string
name string
desc string
l *zap.SugaredLogger
}
type cronFunc struct {
*BaseJob
fn func(ctx context.Context, l *zap.SugaredLogger) error
}
func addError(fn func(ctx context.Context, l *zap.SugaredLogger)) func(context.Context, *zap.SugaredLogger) error {
return func(ctx context.Context, l *zap.SugaredLogger) error {
l.Info("start")
defer l.Info("end")
fn(ctx, l)
return nil
}
}
func (f cronFunc) Run() error { return f.fn(f.ctx, f.l) }
func NewCronFunc(ctx context.Context, name, desc string, fn func(ctx context.Context, l *zap.SugaredLogger)) cronFunc {
return cronFunc{
BaseJob: NewBaseJob(ctx, name, desc),
fn: addError(fn),
}
}
func NewCronFuncWithError(ctx context.Context, name, desc string, fn func(ctx context.Context, l *zap.SugaredLogger) error) cronFunc {
return cronFunc{
BaseJob: NewBaseJob(ctx, name, desc),
fn: fn,
}
}
func NewBaseJob(ctx context.Context, name, desc string) *BaseJob {
return &BaseJob{
ctx: ctx,
name: name,
desc: desc,
l: log.ZapLogS.Named(name),
}
}
func (j *BaseJob) Name() string { return j.name }
func (j *BaseJob) Desc() string { return j.desc }
func (j *BaseJob) L() *zap.SugaredLogger { return j.l }
func (j *BaseJob) Context() context.Context { return j.ctx }
type CronLogger struct {
l *zap.SugaredLogger
}
func NewCronLogger() *CronLogger {
return &CronLogger{
l: log.ZapLogS.Named("cron"),
}
}
func (l *CronLogger) Info(msg string, kvs ...interface{}) {
l.l.Infow(msg, kvs)
}
func (l *CronLogger) Error(err error, msg string, kvs ...interface{}) {
l.l.Errorw(err.Error()+msg, kvs)
}
func StartJobs(jobs ...CronJobAndSpec) (closer func()) {
var once sync.Once
l := NewCronLogger()
c := cron.New(cron.WithSeconds(), cron.WithLocation(time.Local), cron.WithChain(cron.Recover(l)))
for i := range jobs {
j := jobs[i].CronJob
spec := jobs[i].Spec
name := j.Name()
_, err := c.AddFunc(spec, func() {
defer log.Infof("%s end", name)
log.Infof("%s start", name)
if err := j.Run(); err != nil {
log.Errorf("%s run failed: %s", name, err.Error())
}
})
if err != nil {
log.Fatalf("%s add failed: %s", err)
}
}
c.Start()
return func() {
once.Do(func() {
c.Stop()
})
}
}
type CronFuncType func(ctx context.Context, l *zap.SugaredLogger)
func TraceTime(fn CronFuncType) CronFuncType {
return func(ctx context.Context, l *zap.SugaredLogger) {
now := time.Now()
defer func() {
elapse := time.Since(now)
if elapse > time.Second {
l.Infof("takes %s", elapse.Truncate(time.Millisecond))
} else {
l.Infof("takes %d ms", elapse/time.Millisecond)
}
}()
l.Info("start run")
fn(ctx, l)
l.Info("end run")
}
}
// MustSecondSpec is a helper method to validate spec at compile time
func MustSecondSpec(spec string) string {
parser := cron.NewParser(
cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)
_, err := parser.Parse(spec)
if err != nil {
panic(err)
}
return spec
}