You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在ASP.NET Core 7与Angular 15中实现用户自定义时间的定期短信提醒

解决方案思路

核心逻辑:基于数据库存储的用户提醒规则动态调度

你的思路完全可行——将用户的提醒规则(周期、发送时间、收件人、短信内容等)持久化到数据库,再通过调度框架(Hangfire/Quartz)根据这些规则动态创建定时任务,完全支持多用户、多提醒的场景。以下是分框架的具体实现步骤:

一、用Hangfire实现动态定时任务

Hangfire支持在业务逻辑层动态创建Cron作业,无需在Program.cs硬编码参数,适配用户自定义需求:

  1. 配置Hangfire到Program.cs
builder.Services.AddHangfire(config => config
    .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
builder.Services.AddHangfireServer();
  1. 编写短信发送的后台任务服务
    创建专门的服务类,负责从数据库读取提醒信息并调用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();
    }
}
  1. 用户提交提醒时动态创建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同样支持动态创建触发器,核心逻辑一致:

  1. 定义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);
    }
}
  1. 在接口中创建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使用的关键注意事项

  1. sendAt的限制:Twilio预定短信最多只能提前7天,无法满足长周期重复提醒(如每月一次),因此必须依赖后端调度框架定期触发发送。
  2. 状态回调的正确用法: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 12:23:20