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

C#实现依赖型顺序API调用及令牌传递方案问询

问题描述

我需要借助Azure Function App每日执行一串依赖顺序的API调用,最终将文件下载至存储账户。目前已实现AuthHttpTrigger函数,可通过授权码获取refresh token并存储到Azure存储表(也可改用密钥保管库),该功能运行正常。

接下来需要完成以下步骤:

  • 读取存储的refresh token,调用API获取新的access token,并将返回的新refresh token更新至存储表;
  • 使用access token调用API创建报告,生成reportId;
  • 携带access token和reportId调用API获取文件的安全下载链接(需通过定时器轮询直到链接非空)。

由于每个API调用依赖前一个的输出参数,想知道有没有简便的顺序调用实现方式?我当前思路是创建另一个函数,为每个API调用拆分任务,使用await Task和Task.WhenAll,同时用定时器等待下载链接。

最终需求:手动触发一次授权流程后,利用refresh token每日自动更新(access token每日过期),完成上述完整流程。

涉及的API:

  1. /api/AuthHttpTrigger - 手动交互传入授权码,返回access token、refresh token和id token;
  2. https://login.windows.net/{app id}/oauth2/token - 传入refresh token获取新的access token;
  3. https://api.partnercenter.microsoft.com/insights/v1/mpn/ScheduledReport - 传入access token创建报告,生成reportId;
  4. https://api.partnercenter.microsoft.com/insights/v1/mpn/ScheduledReport/execution/{report id} - 传入access token和reportId获取下载链接。
实现方案建议
  1. 用Timer Trigger作为每日执行入口:创建Timer Trigger函数,配置CRON表达式(如0 0 0 * * *实现每日凌晨执行),作为整个流程的启动点。
  2. 顺序调用直接用await链式执行:因每个步骤依赖前一步输出,无需Task.WhenAll(该方法适用于并行任务),直接用await逐个调用即可,逻辑清晰且实现简单。
  3. 轮询下载链接添加重试机制:用循环+Task.Delay实现轮询,同时设置最大重试次数和间隔时间,避免无限循环。
  4. 完善存储表的token读取逻辑:现有AzureStorageClient缺少读取refresh token的方法,需补充该方法获取存储的token。
完整代码实现

1. 补充AzureStorageClient的token读取方法

using Microsoft.Azure.Cosmos.Table;
using System.Threading.Tasks;

namespace PartnerCenterAPI.Properties
{
    public class AzureStorageClient
    {
        private readonly CloudTable refreshTokenTable;

        public AzureStorageClient(string connectionString)
        {
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration());

            refreshTokenTable = tableClient.GetTableReference("refreshTokens");
            refreshTokenTable.CreateIfNotExists();
        }

        public async Task<RefreshTokenEntity> AddOrUpdateRefreshToken(string audience, string userId, string refreshToken)
        {
            RefreshTokenEntity tokenEntity = new(audience, userId)
            {
                RefreshToken = refreshToken
            };

            TableResult tableResult = await refreshTokenTable.ExecuteAsync(TableOperation.InsertOrReplace(tokenEntity));

            return tableResult.Result as RefreshTokenEntity;
        }

        // 新增:读取存储的refresh token
        public async Task<RefreshTokenEntity> GetRefreshToken(string audience, string userId)
        {
            TableOperation retrieveOperation = TableOperation.Retrieve<RefreshTokenEntity>(audience, userId);
            TableResult result = await refreshTokenTable.ExecuteAsync(retrieveOperation);
            return result.Result as RefreshTokenEntity;
        }
    }
}

2. 创建PartnerCenterApiClient封装报告相关API

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace PartnerCenterAPI.Properties
{
    public class PartnerCenterApiClient
    {
        private static readonly HttpClient httpClient = new();
        private readonly string baseUrl = "https://api.partnercenter.microsoft.com/insights/v1/mpn";

        public async Task<string> CreateScheduledReport(string accessToken)
        {
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            
            // 根据实际API要求构造请求体,此处为示例,需替换为真实请求内容
            var requestContent = new StringContent(JsonConvert.SerializeObject(new {}), System.Text.Encoding.UTF8, "application/json");
            var response = await httpClient.PostAsync($"{baseUrl}/ScheduledReport", requestContent);
            
            response.EnsureSuccessStatusCode();
            
            var responseData = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
            return responseData.reportId; // 根据实际返回结构调整字段名
        }

        public async Task<string> GetReportDownloadUrl(string accessToken, string reportId, int maxRetries = 10, int delaySeconds = 30)
        {
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            
            for (int i = 0; i < maxRetries; i++)
            {
                var response = await httpClient.GetAsync($"{baseUrl}/ScheduledReport/execution/{reportId}");
                response.EnsureSuccessStatusCode();
                
                var responseData = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
                string downloadUrl = responseData.downloadUrl; // 根据实际返回结构调整字段名
                
                if (!string.IsNullOrEmpty(downloadUrl))
                {
                    return downloadUrl;
                }
                
                await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
            }
            
            throw new Exception("达到最大重试次数,未获取到有效下载链接");
        }

        // 新增:根据下载链接下载文件到Azure存储账户
        public async Task DownloadFileToStorage(string downloadUrl, string storageConnectionString, string containerName, string fileName)
        {
            var blobClient = new Azure.Storage.Blobs.BlobContainerClient(storageConnectionString, containerName);
            await blobClient.CreateIfNotExistsAsync();
            
            using var httpResponse = await httpClient.GetAsync(downloadUrl);
            httpResponse.EnsureSuccessStatusCode();
            
            var blob = blobClient.GetBlobClient(fileName);
            await blob.UploadAsync(httpResponse.Content, overwrite: true);
        }
    }
}

3. 创建Timer Trigger函数作为每日执行入口

using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;

namespace PartnerCenterAPI.Properties
{
    public static class DailyReportFunction
    {
        [FunctionName("DailyReportFunction")]
        public static async Task Run(
            [TimerTrigger("0 0 0 * * *")] TimerInfo myTimer,
            ILogger log)
        {
            log.LogInformation($"Daily report process started at: {DateTime.Now}");

            // 从环境变量获取配置
            string tenantId = Environment.GetEnvironmentVariable("TenantId", EnvironmentVariableTarget.Process);
            string clientId = Environment.GetEnvironmentVariable("ClientId", EnvironmentVariableTarget.Process);
            string clientSecret = Environment.GetEnvironmentVariable("ClientSecret", EnvironmentVariableTarget.Process);
            string storageConnectionString = Environment.GetEnvironmentVariable("TableStorage", EnvironmentVariableTarget.Process);
            string blobStorageConnectionString = Environment.GetEnvironmentVariable("BlobStorage", EnvironmentVariableTarget.Process);
            string blobContainerName = "reports"; // 存储文件的容器名,可配置为环境变量

            try
            {
                // 1. 读取存储的refresh token
                var storageClient = new AzureStorageClient(storageConnectionString);
                // userId需与AuthHttpTrigger中存储的一致,可从环境变量或存储中获取,此处为示例值
                string userId = "your-user-id"; 
                var tokenEntity = await storageClient.GetRefreshToken(clientId, userId);
                
                if (tokenEntity == null || string.IsNullOrEmpty(tokenEntity.RefreshToken))
                {
                    throw new Exception("未找到有效的refresh token,请先执行AuthHttpTrigger获取授权");
                }

                // 2. 使用refresh token获取新的access token和refresh token
                var identityClient = new MicrosoftIdentityClient(clientId, clientSecret, tenantId);
                var (newIdToken, newAccessToken, newRefreshToken) = await identityClient.GetAccessTokenFromRefreshToken(tokenEntity.RefreshToken);
                
                // 更新存储的refresh token
                await storageClient.AddOrUpdateRefreshToken(clientId, userId, newRefreshToken);
                log.LogInformation("Refresh token已更新");

                // 3. 创建报告获取reportId
                var partnerCenterClient = new PartnerCenterApiClient();
                string reportId = await partnerCenterClient.CreateScheduledReport(newAccessToken);
                log.LogInformation($"报告创建成功,reportId: {reportId}");

                // 4. 轮询获取下载链接
                string downloadUrl = await partnerCenterClient.GetReportDownloadUrl(newAccessToken, reportId);
                log.LogInformation($"获取到下载链接: {downloadUrl}");

                // 5. 下载文件到Blob存储
                string fileName = $"report_{DateTime.Now:yyyyMMdd}.csv"; // 根据实际文件类型调整后缀
                await partnerCenterClient.DownloadFileToStorage(downloadUrl, blobStorageConnectionString, blobContainerName, fileName);
                log.LogInformation($"文件已成功下载至存储账户,文件名: {fileName}");
            }
            catch (Exception ex)
            {
                log.LogError($"流程执行失败: {ex.Message}");
                throw;
            }

            log.LogInformation($"Daily report process completed at: {DateTime.Now}");
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:57:20