114 lines
3.7 KiB
Go
114 lines
3.7 KiB
Go
package jobs
|
||
|
||
import (
|
||
"ai_scheduler/internal/biz"
|
||
"ai_scheduler/internal/config"
|
||
"ai_scheduler/internal/entitys"
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/robfig/cron/v3"
|
||
)
|
||
|
||
// AdviceTaskJob 数字销售定时任务
|
||
// 1. 客户等级评估:每日 daily_evaluate_hour 点(默认 0 点)评估熟客/意向/沉睡/非客户;
|
||
// 2. 熟客超时兜底:每 2 分钟扫描「人工超时未回复」的熟客,执行礼貌性回复与简单解答;
|
||
// 3. 对话完成画像迭代:每 10 分钟扫描闲置结束的对话,分析聊天记录完善客户画像(同时抽取时间约定→跟进线索);
|
||
// 4. 主动触达:每小时扫描生日/新活动,AI 主动发起对话(受活跃时段与总开关限制);
|
||
// 5. 跟进线索到点执行:每分钟扫描到期约定,remind_customer 由 AI 提醒客户、offline_arrival 转后台待办。
|
||
type AdviceTaskJob struct {
|
||
cfg *config.Config
|
||
evaluateBiz *biz.AdviceEvaluateBiz
|
||
strategyBiz *biz.AdviceStrategyBiz
|
||
proactiveBiz *biz.AdviceProactiveBiz
|
||
iterateBiz *biz.AdviceIterateBiz
|
||
followBiz *biz.AdviceFollowBiz
|
||
cron *cron.Cron
|
||
}
|
||
|
||
func NewAdviceTaskJob(
|
||
cfg *config.Config,
|
||
evaluateBiz *biz.AdviceEvaluateBiz,
|
||
strategyBiz *biz.AdviceStrategyBiz,
|
||
proactiveBiz *biz.AdviceProactiveBiz,
|
||
iterateBiz *biz.AdviceIterateBiz,
|
||
followBiz *biz.AdviceFollowBiz,
|
||
) *AdviceTaskJob {
|
||
return &AdviceTaskJob{
|
||
cfg: cfg,
|
||
evaluateBiz: evaluateBiz,
|
||
strategyBiz: strategyBiz,
|
||
proactiveBiz: proactiveBiz,
|
||
iterateBiz: iterateBiz,
|
||
followBiz: followBiz,
|
||
// SkipIfStillRunning:上一轮未完成时跳过本轮,避免任务堆积
|
||
cron: cron.New(cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger))),
|
||
}
|
||
}
|
||
|
||
// Start 启动调度(非阻塞;服务退出时调用 Stop)
|
||
func (j *AdviceTaskJob) Start() {
|
||
// 1. 客户等级评估:cron 五段式「分 时 日 月 周」,每天 hour 点整执行
|
||
hour := j.cfg.Advicer.DailyEvaluateHour
|
||
if hour < 0 || hour > 23 {
|
||
hour = 0
|
||
}
|
||
spec := fmt.Sprintf("0 %d * * *", hour)
|
||
if _, err := j.cron.AddFunc(spec, func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||
defer cancel()
|
||
_, _ = j.evaluateBiz.EvaluateAll(ctx, &entitys.AdvicerClientEvaluateReq{})
|
||
}); err != nil {
|
||
// spec 由校验后的 hour 生成,正常不会失败
|
||
return
|
||
}
|
||
|
||
// 2. 熟客超时兜底:每 2 分钟扫描一次
|
||
if _, err := j.cron.AddFunc("*/2 * * * *", func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||
defer cancel()
|
||
_, _ = j.strategyBiz.RegularTimeoutScan(ctx, false)
|
||
}); err != nil {
|
||
return
|
||
}
|
||
|
||
// 3. 对话完成画像迭代:每 10 分钟扫描一次
|
||
if _, err := j.cron.AddFunc("*/10 * * * *", func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
|
||
defer cancel()
|
||
_, _ = j.iterateBiz.Run(ctx, &entitys.AdvicerIterateRunReq{})
|
||
}); err != nil {
|
||
return
|
||
}
|
||
|
||
// 4. 主动触达(生日/活动):每小时整点扫描(内部校验活跃时段与开关)
|
||
if _, err := j.cron.AddFunc("0 * * * *", func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||
defer cancel()
|
||
_, _ = j.proactiveBiz.Scan(ctx, &entitys.AdvicerProactiveRunReq{})
|
||
}); err != nil {
|
||
return
|
||
}
|
||
|
||
// 5. 跟进线索到点执行:每分钟扫描到期约定(AI 提醒客户 / 线下转后台待办)
|
||
if j.followBiz != nil {
|
||
if _, err := j.cron.AddFunc("* * * * *", func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||
defer cancel()
|
||
_, _ = j.followBiz.RunDue(ctx)
|
||
}); err != nil {
|
||
return
|
||
}
|
||
}
|
||
|
||
j.cron.Start()
|
||
}
|
||
|
||
// Stop 停止调度
|
||
func (j *AdviceTaskJob) Stop() {
|
||
if j.cron != nil {
|
||
j.cron.Stop()
|
||
}
|
||
}
|