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

Azure Web App静态变量处理及SharePoint令牌持久化问题

问题分析

你遇到的核心问题是:Azure Web App环境下,静态变量无法稳定持久化令牌。原因包括:

  • Web App可能运行多实例,每个实例有独立的静态变量副本,请求分发到不同实例时会重复生成令牌
  • 应用会因内存回收、部署更新、配置变更等原因重启进程,静态变量会被清空
  • 即使开启Always On,也无法避免进程重启或多实例场景的影响
解决方案

下面是几种可靠的令牌存储方案,按实用性排序:

1. 内存缓存(IMemoryCache)

这是替代静态变量最简单的方案,支持设置过期时间,逻辑可控性更强,单实例场景下足够稳定。

改造后的代码示例:

public class MySharePointService : IMySharepointService
{
    private readonly IMyLogService _myLogService;
    private readonly IMemoryCache _cache;
    private const string TokenCacheKey = "SharePointAccessToken";

    // 构造函数注入IMemoryCache
    public MySharePointService(IMyLogService myLogService, IMemoryCache cache)
    {
        _myLogService = myLogService;
        _cache = cache;
    }

    private async Task GenerateAccessTokenIfNeeded()
    {
        // 先检查缓存里的令牌是否有效
        if (_cache.TryGetValue(TokenCacheKey, out string existingToken) && !string.IsNullOrEmpty(existingToken))
        {
            try
            {
                using (var context = new AuthenticationManager().GetAccessTokenContext(_siteUrl, existingToken))
                {
                    context.ExecuteQuery();
                    return; // 令牌能用,直接返回
                }
            }
            catch (Exception)
            {
                _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Information", "缓存令牌失效,将生成新令牌");
                _cache.Remove(TokenCacheKey); // 删掉失效令牌
            }
        }

        // 生成新令牌
        try
        {
            if (string.IsNullOrEmpty(_siteUrl) || string.IsNullOrEmpty(_clientId) || string.IsNullOrEmpty(_clientSecret))
            {
                _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Error", "生成新令牌失败,环境变量为空");
                return;
            }

            using (var context = new AuthenticationManager().GetACSAppOnlyContext(_siteUrl, _clientId, _clientSecret))
            {
                var newToken = context.GetAccessToken();
                // 设置缓存过期时间为22小时(比最大有效期24小时短,留缓冲时间)
                var cacheOptions = new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromHours(22));
                _cache.Set(TokenCacheKey, newToken, cacheOptions);
                
                _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Information", $"新令牌已生成并缓存");
            }
        }
        catch (Exception e)
        {
            _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Error", $"生成新令牌失败: {e.Message}");
        }
    }
}

注意:如果Web App是多实例部署,每个实例会有独立的内存缓存,还是可能重复生成令牌,但比静态变量稳定得多。需要跨实例共享的话,用下面的分布式缓存方案。

2. 分布式缓存(IDistributedCache + Azure Redis)

如果你的Web App是多实例部署,推荐用Azure Redis Cache做分布式缓存,确保所有实例共享同一个令牌缓存,彻底解决重复生成问题。

步骤:

  1. 在Azure门户创建Redis缓存实例
  2. 项目中安装Microsoft.Extensions.Caching.StackExchangeRedis NuGet包
  3. 在Web App的应用设置里配置Redis连接字符串
  4. 注入IDistributedCache并替换内存缓存逻辑

核心代码示例:

public class MySharePointService : IMySharepointService
{
    private readonly IMyLogService _myLogService;
    private readonly IDistributedCache _distributedCache;
    private const string TokenCacheKey = "SharePointAccessToken";

    public MySharePointService(IMyLogService myLogService, IDistributedCache distributedCache)
    {
        _myLogService = myLogService;
        _distributedCache = distributedCache;
    }

    private async Task GenerateAccessTokenIfNeeded()
    {
        string existingToken = await _distributedCache.GetStringAsync(TokenCacheKey);
        if (!string.IsNullOrEmpty(existingToken))
        {
            try
            {
                using (var context = new AuthenticationManager().GetAccessTokenContext(_siteUrl, existingToken))
                {
                    context.ExecuteQuery();
                    return;
                }
            }
            catch (Exception)
            {
                _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Information", "分布式缓存令牌失效,将生成新令牌");
                await _distributedCache.RemoveAsync(TokenCacheKey);
            }
        }

        // 生成新令牌
        try
        {
            if (string.IsNullOrEmpty(_siteUrl) || string.IsNullOrEmpty(_clientId) || string.IsNullOrEmpty(_clientSecret))
            {
                _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Error", "生成新令牌失败,环境变量为空");
                return;
            }

            var newToken = await GenerateNewToken();
            // 设置缓存过期时间
            var cacheOptions = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromHours(22));
            await _distributedCache.SetStringAsync(TokenCacheKey, newToken, cacheOptions);
            
            _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Information", $"新令牌已生成并存入分布式缓存");
        }
        catch (Exception e)
        {
            _myLogService.Log("SharepointService", "GenerateAccessTokenIfNeeded", "Error", $"生成新令牌失败: {e.Message}");
        }
    }

    // 抽出生成令牌的独立方法,提高代码可读性
    private async Task<string> GenerateNewToken()
    {
        using (var context = new AuthenticationManager().GetACSAppOnlyContext(_siteUrl, _clientId, _clientSecret))
        {
            return context.GetAccessToken();
        }
    }
}

3. 应用设置存储(不推荐)

可以把令牌存在Web App的应用设置里,但缺点明显:

  • 更新应用设置会触发应用重启
  • 令牌有效期内需要手动或通过代码更新,灵活性差
  • 不适合频繁更新的场景,仅作为备选方案
额外优化建议
  • 抽出生成令牌的逻辑为独立方法,提升代码可维护性
  • 主动跟踪令牌过期时间,在令牌过期前1小时左右主动刷新,避免请求时才发现令牌失效
  • 处理并发请求:当多个请求同时检测到令牌失效时,用锁(单实例)或分布式锁(Redis)避免重复生成令牌

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 22:02:13