Laravel:如何在会议开始前1小时精准发送提醒邮件?
实现会议开始前1小时精准发送提醒邮件的方案
针对你的需求,这里提供几种生产环境可用的实现方案,覆盖不同复杂度和场景:
方案一:定时任务轮询数据库(简单易实现)
适合中小规模场景,实现成本低,无需额外中间件。
步骤:
- 在
meeting表新增reminder_sent字段(布尔型,默认false),标记是否已发送提醒,避免重复触发。 - 编写定时任务,每隔固定时间(比如1分钟)执行以下逻辑:
- 查询所有
start_time在「当前时间~当前时间+1小时」范围内,且reminder_sent = false的会议记录 - 对每条记录调用邮件发送接口
- 发送成功后更新
reminder_sent为true
- 查询所有
代码示例(Python + APScheduler):
import datetime import mysql.connector from apscheduler.schedulers.background import BackgroundScheduler import smtplib from email.mime.text import MIMEText # 数据库连接配置 DB_CONFIG = { 'user': 'your_user', 'password': 'your_pwd', 'host': 'localhost', 'database': 'your_db' } # 邮件发送函数 def send_reminder_email(meeting_id, start_time, attendee_email): msg = MIMEText(f"提醒:会议ID {meeting_id} 将在 {start_time.strftime('%Y-%m-%d %H:%M')} 开始,请准时参加。") msg['Subject'] = '会议即将开始提醒' msg['From'] = 'reminder@yourcompany.com' msg['To'] = attendee_email with smtplib.SMTP('smtp.yourcompany.com', 587) as server: server.starttls() server.login('smtp_user', 'smtp_pwd') server.send_message(msg) # 定时任务核心逻辑 def check_and_send_reminders(): conn = mysql.connector.connect(**DB_CONFIG) cursor = conn.cursor(dictionary=True) now = datetime.datetime.now(datetime.timezone.utc) reminder_time = now + datetime.timedelta(hours=1) # 查询符合条件的会议(确保时区统一,这里假设start_time为UTC时间) query = """ SELECT id, start_time, attendee_email FROM meeting WHERE start_time BETWEEN %s AND %s AND reminder_sent = FALSE """ cursor.execute(query, (now, reminder_time)) meetings = cursor.fetchall() for meeting in meetings: try: send_reminder_email(meeting['id'], meeting['start_time'], meeting['attendee_email']) # 更新已发送标记 update_query = "UPDATE meeting SET reminder_sent = TRUE WHERE id = %s" cursor.execute(update_query, (meeting['id'],)) conn.commit() except Exception as e: print(f"发送会议 {meeting['id']} 提醒失败:{str(e)}") # 可记录日志,后续手动重试 cursor.close() conn.close() # 启动定时任务,每分钟执行一次 scheduler = BackgroundScheduler(timezone='UTC') scheduler.add_job(check_and_send_reminders, 'interval', minutes=1) scheduler.start() # 保持进程运行 try: while True: datetime.datetime.now() except (KeyboardInterrupt, SystemExit): scheduler.shutdown()
数据库层面实现(MySQL事件调度器):
-- 开启事件调度器 SET GLOBAL event_scheduler = ON; -- 创建每分钟执行一次的事件 CREATE EVENT meeting_reminder_event ON SCHEDULE EVERY 1 MINUTE STARTS CURRENT_TIMESTAMP DO BEGIN DECLARE done INT DEFAULT FALSE; DECLARE meeting_id INT; DECLARE start_time DATETIME; DECLARE attendee_email VARCHAR(255); -- 游标查询待提醒会议 DECLARE cur CURSOR FOR SELECT id, start_time, attendee_email FROM meeting WHERE start_time BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 1 HOUR) AND reminder_sent = FALSE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO meeting_id, start_time, attendee_email; IF done THEN LEAVE read_loop; END IF; -- 调用自定义邮件发送存储过程(需提前实现) CALL send_meeting_reminder(meeting_id, start_time, attendee_email); -- 更新已发送标记 UPDATE meeting SET reminder_sent = TRUE WHERE id = meeting_id; END LOOP; CLOSE cur; END;
方案二:延迟消息队列(精准高效,适合大规模场景)
避免轮询的资源消耗,精准在指定时间触发提醒,同时支持会议修改/取消的场景。
步骤:
- 新增会议时,计算提醒时间:
reminder_time = start_time - 1小时 - 计算当前时间到
reminder_time的时间差,将会议信息(ID、参会人邮箱、会议时间等)发送到延迟队列,设置延迟时间为该差值 - 消费者服务监听队列,延迟时间到期后取出消息并发送提醒邮件
- 若会议被修改或取消,需从队列中删除对应的延迟消息(或标记为无效,消费时跳过)
代码示例(Redis延迟队列,Python):
import datetime import redis import json import time from threading import Thread # Redis连接配置 r = redis.Redis(host='localhost', port=6379, db=0) # 新增会议时添加延迟任务 def add_reminder_task(meeting_id, start_time, attendee_email): reminder_time = start_time - datetime.timedelta(hours=1) delay = (reminder_time - datetime.datetime.now(datetime.timezone.utc)).total_seconds() if delay > 0: task = json.dumps({ 'meeting_id': meeting_id, 'attendee_email': attendee_email, 'start_time': start_time.isoformat() }) # 存入有序集合,score为提醒时间的时间戳 r.zadd('meeting_reminders', {task: reminder_time.timestamp()}) # 消费者线程,持续处理到期任务 def consumer_thread(): while True: now = datetime.datetime.now(datetime.timezone.utc).timestamp() # 取出所有到期任务 tasks = r.zrangebyscore('meeting_reminders', 0, now, start=0, num=10) if tasks: for task in tasks: task_data = json.loads(task) try: send_reminder_email( task_data['meeting_id'], datetime.datetime.fromisoformat(task_data['start_time']), task_data['attendee_email'] ) # 发送成功后删除任务 r.zrem('meeting_reminders', task) except Exception as e: print(f"处理任务失败:{str(e)}") # 可将任务重新加入队列,或记录日志后续处理 time.sleep(10) # 每10秒检查一次 # 启动消费者线程 Thread(target=consumer_thread, daemon=True).start() # 会议修改时更新提醒任务 def update_meeting_reminder(old_meeting_id, new_start_time, attendee_email): # 删除旧任务 all_tasks = r.zrange('meeting_reminders', 0, -1) for task in all_tasks: task_data = json.loads(task) if task_data['meeting_id'] == old_meeting_id: r.zrem('meeting_reminders', task) break # 添加新任务 add_reminder_task(old_meeting_id, new_start_time, attendee_email)
关键注意事项
- 时区统一:确保数据库
start_time、应用程序时间、调度任务时间使用同一时区(推荐UTC),避免时间偏差 - 重复提醒避免:通过
reminder_sent字段或队列任务删除机制,确保每个会议只发送一次提醒 - 异常处理:邮件发送失败时添加重试机制(如间隔重试3次),或记录到失败队列后续人工处理
- 会议变更处理:会议时间修改或取消时,及时更新/删除对应的提醒任务
- 日志记录:记录所有提醒发送的成功/失败情况,便于排查问题
内容的提问来源于stack exchange,提问作者eksoo
相关产品推荐
相关产品推荐

