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

定时器触发Azure Function按配置时间调用POST API启动App Service的实现问题

解决方案

首先修正现有代码的两处基础错误:

  • 节假日判断逻辑写反:现有if (holidaychk != 0)的含义是「当天是节假日才执行后续逻辑」,和需求的「当日不属于节假日」不符,需修改为if (holidaychk == 0)
  • SQL语句语法错误:settingscheck变量的拼接缺空格,拼接后会出现OrderDayFROM的错误语法,需要在第一行SQL末尾加空格

推荐方案:使用Durable Function实现无超时的定时等待

普通Azure Function(消费计划)最长执行时间仅为10分钟,如果StartTime距离早5点的间隔超过10分钟,直接使用Thread.Sleep/Task.Delay会触发执行超时,任务被强制终止。Durable Function的持久定时器机制不需要占用执行资源,不受普通执行超时限制,是最适配该场景的方案。

实现步骤:

  1. 安装Durable Function依赖NuGet包:Microsoft.Azure.WebJobs.Extensions.DurableTask
  2. 调整代码结构,拆分三个部分:定时触发的客户端函数、协调器函数、调用启动API的活动函数
  3. 代码示例:
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.Logging;
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Core;
using System.Net.Http;
using System.Net.Http.Headers;

public static class StartorStopSMAppService
{
    // 原定时触发函数作为Durable客户端
    [FunctionName("StartorStopSMAppService_TimerTrigger")]
    public static async Task Run([TimerTrigger("0 0 5 * * *")] TimerInfo myTimer,
        [DurableClient] IDurableOrchestrationClient starter,
        ILogger log)
    {
        int holidaychk = 0;
        var str = Environment.GetEnvironmentVariable("SurplusMouseDBConnection");
        using (SqlConnection conn = new SqlConnection(str))
        {
            try
            {
                conn.Open();
                var holidaycheck = "SELECT Count(*) FROM[dbo].[HolidayWeeks] where FORMAT(getdate(), 'yyyy-MM-dd') = [HolidateDate]";
                SqlCommand cmd1 = new SqlCommand(holidaycheck, conn);
                holidaychk = (Int32)cmd1.ExecuteScalar();

                // 非节假日才继续判断
                if (holidaychk == 0)
                {
                    var settingscheck = "SELECT TOP 1 [EndTime],[StartTime],[OrderDay] " +
                                         "FROM[dbo].[Settings] " +
                                          "where SUBSTRING(DATENAME(weekday, getdate() AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time'), 0, 4) = OrderDay";
                    SqlCommand command = new SqlCommand(settingscheck, conn);
                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        if (reader.Read() && reader.HasRows)
                        {
                            var starttimeStr = reader["StartTime"].ToString();
                            // 解析StartTime为今日的DateTime
                            var today = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.UtcNow, "Eastern Standard Time").Date;
                            var startTime = today.Add(TimeSpan.Parse(starttimeStr));
                            var now = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.UtcNow, "Eastern Standard Time");
                            
                            // 计算延时时间,如果StartTime已经过了就直接执行
                            var delay = startTime > now ? startTime - now : TimeSpan.Zero;
                            // 启动协调器,传入延时和Web App资源ID
                            string instanceId = await starter.StartNewAsync("StartAppServiceOrchestrator", Tuple.Create(delay, "你的Web App资源ID"));
                            log.LogInformation($"Started orchestration with ID = '{instanceId}', delay = {delay.TotalMinutes} minutes");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                log.LogError(ex.Message);
            }
            finally
            {
                if (conn.State != System.Data.ConnectionState.Closed)
                {
                    conn.Close();
                }
            }
        }
    }

    // 协调器函数
    [FunctionName("StartAppServiceOrchestrator")]
    public static async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext context)
    {
        var input = context.GetInput<Tuple<TimeSpan, string>>();
        var delay = input.Item1;
        var webAppId = input.Item2;

        // 持久等待,不占用执行资源
        if(delay > TimeSpan.Zero)
        {
            await context.CreateTimer(context.CurrentUtcDateTime.Add(delay), CancellationToken.None);
        }

        // 调用活动函数执行API请求
        await context.CallActivityAsync("CallStartAppServiceApi", webAppId);
    }

    // 活动函数:调用App Service启动API
    [FunctionName("CallStartAppServiceApi")]
    public static async Task CallStartApi([ActivityTrigger] string webAppId, ILogger log)
    {
        var credential = new DefaultAzureCredential();
        var token = await credential.GetTokenAsync(new TokenRequestContext(new[] { "https://management.azure.com/.default" }));
        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
        var apiUrl = $"https://management.azure.com{webAppId}/start?api-version=2022-03-01";
        var response = await httpClient.PostAsync(apiUrl, null);
        response.EnsureSuccessStatusCode();
        log.LogInformation("App Service started successfully");
    }
}

替代方案:使用队列存储延时消息

如果不想引入Durable Function依赖,也可以用Azure Queue Storage实现延时:

  • 计算当前时间到StartTime的时间差,将启动任务参数发送到队列,设置消息的InitialVisibilityDelay为对应的延时时长
  • 新增一个队列触发的Azure Function,收到队列消息后执行API调用即可
  • 队列消息最长支持延时7天,完全满足当日延时需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 18:06:07