在ASP.NET Core 7与Angular 15中实现用户自定义时间的定期短信提醒
解决方案思路
核心逻辑:基于数据库存储的用户提醒规则动态调度
你的思路完全可行——将用户的提醒规则(周期、发送时间、收件人、短信内容等)持久化到数据库,再通过调度框架(Hangfire/Quartz)根据这些规则动态创建定时任务,完全支持多用户、多提醒的场景。以下是分框架的具体实现步骤:
一、用Hangfire实现动态定时任务
Hangfire支持在业务逻辑层动态创建Cron作业,无需在Program.cs硬编码参数,适配用户自定义需求:
- 配置Hangfire到Program.cs
builder.Services.AddHangfire(config => config .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"))); builder.Services.AddHangfireServer();
- 编写短信发送的后台任务服务
创建专门的服务类,负责从数据库读取提醒信息并调用Twilio发送:
public class SmsReminderService { private readonly TwilioRestClient _twilioClient; private readonly YourDbContext _dbContext; public SmsReminderService(TwilioRestClient twilioClient, YourDbContext dbContext) { _twilioClient = twilioClient; _dbContext = dbContext; } public async Task SendScheduledReminder(Guid reminderId) { var reminder = await _dbContext.MedicationReminders.FindAsync(reminderId); if (reminder == null || !reminder.IsActive) return; // 调用Twilio发送短信 var message = await MessageResource.CreateAsync( messagingServiceSid: "XXXXXXXXXXX", to: new PhoneNumber(reminder.PhoneNumber), from: new PhoneNumber(reminder.FromNumber), body: reminder.MessageContent, client: _twilioClient); // 记录发送日志 _dbContext.SmsLogs.Add(new SmsLog { ReminderId = reminderId, TwilioMessageSid = message.Sid, SentTime = DateTime.UtcNow }); await _dbContext.SaveChangesAsync(); } }
- 用户提交提醒时动态创建Hangfire任务
在API接口中,保存用户提醒规则后直接创建/更新定时任务:
[HttpPost("CreateReminder")] public async Task<IActionResult> CreateReminder([FromBody] MedicationReminderDto dto) { // 保存提醒规则到数据库 var reminder = new MedicationReminder { PhoneNumber = dto.PhoneNumber, FromNumber = dto.FromNumber, MessageContent = dto.MessageContent, CronExpression = dto.CronExpression, // 前端转换用户周期为标准Cron表达式,如每天8点为"0 8 * * *" IsActive = true }; _dbContext.MedicationReminders.Add(reminder); await _dbContext.SaveChangesAsync(); // 绑定提醒ID创建定时任务 RecurringJob.AddOrUpdate( jobId: $"Reminder_{reminder.Id}", methodCall: () => _smsReminderService.SendScheduledReminder(reminder.Id), cronExpression: reminder.CronExpression, timeZone: TimeZoneInfo.Local); // 根据用户时区调整 return Ok("提醒已创建"); }
二、用Quartz实现的备选方案
Quartz同样支持动态创建触发器,核心逻辑一致:
- 定义Job类
public class SmsReminderJob : IJob { private readonly SmsReminderService _reminderService; public SmsReminderJob(SmsReminderService reminderService) { _reminderService = reminderService; } public async Task Execute(IJobExecutionContext context) { var reminderId = (Guid)context.JobDetail.JobDataMap["ReminderId"]; await _reminderService.SendScheduledReminder(reminderId); } }
- 在接口中创建Job和Trigger
[HttpPost("CreateReminder")] public async Task<IActionResult> CreateReminder([FromBody] MedicationReminderDto dto, IScheduler scheduler) { // 保存数据库逻辑同上... // 构建Job并传入提醒ID var jobDetail = JobBuilder.Create<SmsReminderJob>() .WithIdentity($"ReminderJob_{reminder.Id}") .UsingJobData("ReminderId", reminder.Id) .Build(); // 构建Cron触发器 var trigger = TriggerBuilder.Create() .WithIdentity($"ReminderTrigger_{reminder.Id}") .WithCronSchedule(dto.CronExpression, x => x.InTimeZone(TimeZoneInfo.Local)) .Build(); // 调度任务 await scheduler.ScheduleJob(jobDetail, trigger); return Ok("提醒已创建"); }
三、Twilio使用的关键注意事项
sendAt的限制:Twilio预定短信最多只能提前7天,无法满足长周期重复提醒(如每月一次),因此必须依赖后端调度框架定期触发发送。- 状态回调的正确用法:Twilio的
statusCallback是用来通知短信发送状态(成功/失败)的,不能用来触发重复发送。单独写接口处理状态回调:
[HttpPost("SmsStatusCallback")] public IActionResult SmsStatusCallback([FromForm] TwilioStatusCallbackDto callback) { var log = _dbContext.SmsLogs.FirstOrDefault(l => l.TwilioMessageSid == callback.MessageSid); if (log != null) { log.Status = callback.MessageStatus; _dbContext.SaveChanges(); } return Ok(); }
四、核心细节补充
- 任务更新与停用:用户修改提醒规则或停用提醒时,需同步更新/删除对应的调度任务(如Hangfire用
RecurringJob.Update/RecurringJob.Remove)。 - 时区适配:务必将用户时区转换为服务器可识别的时区,确保Cron表达式按用户预期执行。
- 异常处理:在短信发送方法中添加
try-catch,避免单个任务失败影响整个调度框架,同时记录错误日志。
内容的提问来源于stack exchange,提问作者webdev
相关产品推荐
相关产品推荐

