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

如何在Elixir GenServer中实现每日凌晨12点定时任务

解决Phoenix LiveView中每日凌晨定时执行任务的GenServer实现问题

我有一个LiveView Web应用,希望每日凌晨12点(非工作时间)执行一段代码。已经配置了随Phoenix应用启动的GenServer,但服务器启动时间不固定,无法确定调度的时间范围。

我设想的解决方案是:Phoenix应用启动时捕获当前日期并存储在状态中;GenServer每小时运行一次,检查日期是否变化,若变化则执行目标代码并更新状态,重复此流程。但我对GenServer了解不足,不知道如何按描述存储和比较日期值,甚至猜测可能需要用Agent传递数据。

我已经基于相关示例写了初步代码,但不知道怎么完善:

defmodule App.Periodically do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, DateTime)
  end

  def init(state) do
    schedule_work(state) 
    {:ok, state}
  end

  def handle_info(:work, state) do
    IO.inspect state.utc_now
    IO.inspect "_____________NEW"
    IO.inspect DateTime.utc_now
    # When phoenix app starts - store current date
    # When Genserver runs.....
    # run conditional on stored date with current date
    # If they are different THEN RUN CODE
    # If they are the same - do nothing
    
    IO.inspect state
    schedule_work(state) 
    {:noreply, state}
  end

  defp schedule_work(state) do
   IO.inspect "test"
    Process.send_after(self(), :work, 10000 ) # 1 sec for testing will change to 1 hour
  end
end

完善后的GenServer实现方案

你不需要额外使用Agent,GenServer本身就可以维护状态。下面是调整后的代码,完全实现你的需求:

defmodule App.Periodically do
  use GenServer

  # 启动GenServer,初始化状态为当天UTC日期
  def start_link(_opts) do
    today = DateTime.utc_now() |> Date.truncate(:day)
    GenServer.start_link(__MODULE__, today, name: __MODULE__)
  end

  @impl true
  def init(today) do
    # 首次调度:设置1小时后执行(测试时可改短间隔)
    schedule_next_work()
    {:ok, today}
  end

  @impl true
  def handle_info(:execute_daily_task, last_executed_date) do
    now = DateTime.utc_now()
    today = Date.truncate(now, :day)

    # 检查日期是否更新,更新则执行任务并刷新状态
    {new_state, _} = 
      if Date.compare(today, last_executed_date) == :gt do
        # 替换为你要执行的实际代码
        IO.puts("执行每日凌晨任务:#{now}")
        {today, :executed}
      else
        {last_executed_date, :skipped}
      end

    # 调度下一次检查(每小时执行一次)
    schedule_next_work()
    {:noreply, new_state}
  end

  # 设置下一次任务触发间隔:1小时(3600000毫秒)
  defp schedule_next_work do
    Process.send_after(self(), :execute_daily_task, 3600 * 1000)
  end
end

关键细节说明

  • 状态管理:GenServer的状态直接存储上一次执行任务的日期,无需额外依赖。启动时自动初始化状态为当天UTC日期。
  • 日期对比逻辑:用Date.truncate/2去除时间部分,只保留纯日期结构,避免时分秒干扰;通过Date.compare/2判断日期是否跨天。
  • 调度可靠性:每小时触发一次检查,确保跨天后最多1小时内执行任务,完全覆盖服务器启动时间不固定的场景。
  • 测试调整:若要快速验证逻辑,可将schedule_next_work中的间隔改为10秒,观察日期变化时的执行情况。

集成到Phoenix应用

确保在lib/app/application.ex的children列表中加入该GenServer,让它随应用启动:

children = [
  # ... 其他已有服务
  App.Periodically
]

内容的提问来源于stack exchange,提问作者William

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 16:22:44