งานตามเวลา (Cron Scheduler) ทั้งหมด
ภาพรวม
Profile cron-scheduler เป็น process ที่รวม งานตามเวลา 10 ตัว และ consumer 4 ตัว
(mbox handoff/callback, booking notification/event) ไว้ด้วยกัน
หลักการออกแบบ: cron ส่วนใหญ่ ไม่ทำงานหนักเอง แต่ทำหน้าที่ "หาว่ามีอะไรถึงเวลาแล้วบ้าง" แล้ว publish งานเข้า queue ให้ consumer ตัวจริงทำต่อ — ทำให้ scale งานหนักแยกจากตัวจับเวลาได้ และ cron ที่ยิงซ้อนกัน (หลาย replica) ไม่ทำให้เกิดผลซ้ำ เพราะการจองงานเป็น atomic ที่ระดับ DB/Redis
ใช้ robfig/cron โดยรันตาม local time ของ server เหมือน @Cron ของ NestJS เดิม
(การจัดการ timezone Asia/Bangkok รายกฎอยู่ภายใน ScheduledTriggerScannerService.processRule)
Business Flow
ตาราง cron ทั้ง 10
| Cron | ตาราง/แหล่งข้อมูล | ทำอะไร | เอกสาร |
|---|---|---|---|
*/1 * * * * scheduledTrigger.Run | trigger_rule (source_type='scheduled') | หากฎที่ถึงเวลายิง (คำนวณ timezone รายกฎ) แล้ว publish scheduled_trigger_fire | trigger-engine |
*/1 * * * * scheduledAction.Run | scheduled_action | กู้แถวค้าง 5 นาที, claim 500 แถวด้วย FOR UPDATE SKIP LOCKED, publish scheduled_action_fire | scheduled-action |
0 * * * * campaignStat.Run | Redis CAMPAIGN_STAT_DIRTY:* | หา campaign ที่มีข้อมูลใหม่ publish calculate_campaign_stat | campaign-stat-calculation |
0 * * * * richMenuStat.Run | Redis RICH_MENU_STAT_DIRTY:* | หา rich menu ที่มีข้อมูลใหม่ publish calculate_rich_menu_stat_item | rich-menu-stat-calculation |
*/5 * * * * autoResponseSync.Run | line_oa + auto_response | rebuild Redis hash คีย์เวิร์ดของทุก OA ที่ active (OA ที่พังใช้ continue ไม่ return) | auto-response |
*/1 * * * * mboxTimeout.Run | Redis sorted set agent_sessions | เตือน (stage 1) และปิด session เจ้าหน้าที่ที่หมดเวลา (stage 2) | mbox-agent-handoff |
*/1 * * * * bookingReminder.Run | appointment.booking + journey | หาการจองที่ถึงเวลาเตือน publish booking_notification | booking-notification |
*/1 * * * * bookingNoShow.Run | appointment.booking + journey | หาการจองที่เลยเวลา + grace period แล้ว mark no-show | booking-notification |
*/1 * * * * campaignSchedule.Run | Redis tick key | publish process_campaign 1 ใบ/นาที (dedup ข้าม replica ด้วย SET NX) | campaign-schedule-dispatch |
0 3 * * * loyaltyTier.Run | loyalty.program / tier / account | คำนวณระดับสมาชิกใหม่ทั้งแพลตฟอร์ม (ตัวเดียวที่ลดระดับได้) | loyalty-tier-recalculation |
Consumer 4 ตัวที่รันร่วมกัน
mbox_handoff— ดูที่ mbox-agent-handoffmbox_callback— ดูที่ mbox-callbackbooking_notification— ดูที่ booking-notificationbooking_event_trigger— ดูที่ booking-event-trigger
ลำดับการทำงาน
runCronSchedulerสร้างmq.Client, assert topology- ประกอบ service ทั้งหมด (mbox handoff/callback, booking notification/event, scanner 10 ตัว) โดยแชร์ pgx pool / RedisService / mq publisher / Mbox REST client เดียวกัน
cron.New()แล้วAddFuncทีละ job ตาม expression; error ตอน register จะทำให้ profile หยุดทันทีc.Start()และผูกcontext.AfterFunc(ctx, c.Stop)เพื่อหยุดอย่างสะอาดตอน shutdown- register consumer ทั้ง 4 ลง
mq.Registrarแล้วรันเป็น goroutine
ไฟล์และฟังก์ชันหลัก
cmd/worker/main.go—runCronScheduler(ctx, a)(นิยามjobs []cronJobทั้ง 10 รายการ)internal/cronscheduler/— ไฟล์ละ 1 scanner:scheduled_trigger_scanner.go,scheduled_action_scanner.go,campaign_stat_scanner.go,rich_menu_stat_scanner.go,auto_response_sync.go,mbox_timeout.go,booking_reminder.go,booking_noshow.go,campaign_schedule_scanner.go,loyalty_tier.gointernal/cronscheduler/helpers.go—collectRows()(แปลง pgx rows ให้เป็น[]map[string]any)internal/cronscheduler/health.go— health surface ของ profile- ทดสอบ:
campaign_schedule_scanner_test.go,loyalty_tier_test.go
จุดเชื่อมต่อกับ Service อื่น
- PostgreSQL — ทุก scanner ยกเว้น campaign/rich-menu stat (ซึ่งอ่าน Redis)
- Redis — dirty key ของ stat scanner, tick dedup ของ campaign schedule,
agent_sessionsของ mbox timeout, hash คีย์เวิร์ดของ auto-response - RabbitMQ — publish งานเข้า queue ปลายทาง และ consume 4 queue
- Mbox REST API และ LINE API — ผ่าน mbox / booking service
- ENV:
ENABLE_CRON_SCANNING(toggle ระดับแอป),CRON_SCHEDULER_PORT(default 3003)