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

.Net Core 3.1用RestSharp调用API的Token过期复用异常排查

现有代码问题分析与适配方案

现有代码的核心逻辑缺陷

  • 缓存写入未绑定Token实际过期时间:调用_memoryCache.Set()时没有配置和第三方Token有效期匹配的过期规则,既可能因内存缓存压力被系统自动清理,也可能出现第三方Token早已失效、本地仍反复使用旧值的情况
  • 存在缓存击穿并发风险:当缓存中Token失效/被清理的瞬间,多个并发请求会同时触发Token重获取逻辑,短时间内生成多个全新Token。如果第三方API对同一账号有“仅最近一次生成的Token生效、旧Token立即作废”的策略,就会导致已生成的旧Token全部失效,所有依赖旧Token的请求全部失败,这正是“接口有时完全正常、有时全部失败”的核心诱因
  • 状态判断逻辑错误:CancelCertificateController中判断第三方接口响应状态时,错把控制器自身的Response对象(当前接口返回给前端的响应,默认状态码200)当成了第三方返回的IRestResponse状态码,完全丧失了异常识别能力
  • 结构耦合度高:所有接口控制器通过继承HomeController复用Token获取逻辑,代码重复、维护成本高,无法全局统一管控Token的刷新、异常处理流程

可满足「Token仅过期后重生成、其余时间全局复用」要求的实现方案

核心思路是将Token获取逻辑抽离为全局单例服务,通过信号量锁解决并发击穿问题,将缓存过期时间与第三方Token的实际有效期严格绑定。

1. 定义独立认证服务

首先将Token管理逻辑从控制器中完全剥离,避免控制器继承带来的耦合:

// 认证服务接口
public interface IThirdPartyAuthService
{
    Task<string> GetValidTokenAsync();
}

// 认证服务实现,注册为单例
public class ThirdPartyAuthService : IThirdPartyAuthService
{
    private readonly IMemoryCache _memoryCache;
    private readonly ILogger<ThirdPartyAuthService> _logger;
    // 全局信号量锁,确保同一时间只有一个线程能执行Token刷新逻辑
    private readonly SemaphoreSlim _tokenRefreshLock = new SemaphoreSlim(1, 1);
    private const string TokenCacheKey = "ThirdPartyApi:AuthToken";
    // 第三方Token接口配置
    private const string TokenRequestUrl = "xx";
    private const string Username = "xx";
    private const string Password = "xx";

    public ThirdPartyAuthService(IMemoryCache memoryCache, ILogger<ThirdPartyAuthService> logger)
    {
        _memoryCache = memoryCache;
        _logger = logger;
    }

    public async Task<string> GetValidTokenAsync()
    {
        // 优先从缓存读取有效Token
        if (_memoryCache.TryGetValue(TokenCacheKey, out string cachedToken) && !string.IsNullOrEmpty(cachedToken))
        {
            return cachedToken;
        }

        // 加锁,阻塞其他等待刷新的请求
        await _tokenRefreshLock.WaitAsync();
        try
        {
            // 双重检查:拿到锁后再次确认缓存是否已有新Token,避免重复刷新
            if (_memoryCache.TryGetValue(TokenCacheKey, out cachedToken) && !string.IsNullOrEmpty(cachedToken))
            {
                return cachedToken;
            }

            // 调用第三方接口获取新Token
            var client = new RestClient(TokenRequestUrl);
            var request = new RestRequest(Method.POST)
            {
                RequestFormat = DataFormat.Json
            };
            request.AddParameter("grant_type", "password");
            request.AddParameter("username", Username);
            request.AddParameter("password", Password);

            var response = await client.ExecuteAsync<MyToken>(request);
            if (response.StatusCode != System.Net.HttpStatusCode.OK || response.Data == null || string.IsNullOrEmpty(response.Data.token))
            {
                _logger.LogError("获取第三方Token失败,状态码:{StatusCode}, 错误:{Error}", response.StatusCode, response.ErrorMessage);
                throw new InvalidOperationException("获取第三方认证Token失败");
            }

            // 计算缓存过期时间:第三方返回的expires_in为有效期秒数,预留30秒提前量,避免网络延迟导致请求时刚好过期
            var cacheExpireTime = TimeSpan.FromSeconds(response.Data.expires_in - 30);
            if (cacheExpireTime <= TimeSpan.Zero)
            {
                cacheExpireTime = TimeSpan.FromSeconds(response.Data.expires_in);
            }

            // 写入缓存,绑定绝对过期规则
            _memoryCache.Set(TokenCacheKey, response.Data.token, new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = cacheExpireTime,
                Priority = CacheItemPriority.Normal
            });

            return response.Data.token;
        }
        finally
        {
            // 释放锁资源
            _tokenRefreshLock.Release();
        }
    }
}

2. 服务注册

在Startup.cs的服务配置方法中,将认证服务注册为单例,确保全局所有请求拿到的是同一个服务实例、同一份Token缓存:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
    // 注册认证服务为单例
    services.AddSingleton<IThirdPartyAuthService, ThirdPartyAuthService>();
    services.AddControllers();
}

3. 修正业务接口逻辑

去掉控制器对HomeController的继承,直接注入认证服务获取有效Token,同时修正之前的状态码判断错误:

[Route("api/Certificate/Cancel")]
[Produces("application/json")]
[ApiController]
public class CancelCertificateController : ControllerBase
{
    private readonly IThirdPartyAuthService _authService;
    private readonly ILogger<CancelCertificateController> _logger;

    public CancelCertificateController(IThirdPartyAuthService authService, ILogger<CancelCertificateController> logger)
    {
        _authService = authService;
        _logger = logger;
    }

    [HttpPost]
    [ProducesResponseType(200)]
    [ProducesResponseType(400)]
    public async Task<IActionResult> Post([FromBody] CancelCert cancelCert)
    {
        try
        {
            var validToken = await _authService.GetValidTokenAsync();
            var client = new RestClient("xx");
            var request = new RestRequest(Method.POST);
            request.AddHeader("Accept", "application/json");
            request.AddHeader("Content-Type", "application/json; charset=utf8");
            request.AddHeader("Authorization", $"Bearer {validToken}");
            request.AddHeader("ClientID", "xxx");
            request.AddJsonBody(cancelCert);

            IRestResponse response = await client.ExecuteAsync(request);
            
            // 正确判断第三方接口的返回状态
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                var jObject = JObject.Parse(response.Content);
                return new JsonResult(jObject);
            }
            
            _logger.LogWarning("调用第三方作废证书接口失败,状态码:{StatusCode}, 返回内容:{Content}", response.StatusCode, response.Content);
            return BadRequest($"第三方接口调用失败,状态码:{(int)response.StatusCode}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "作废证书接口处理异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
}

方案匹配性说明

  • 全局单例的缓存机制保证所有接口从同一份缓存中读取Token,只要缓存未过期就完全复用,不会出现Token提前重生成的问题
  • 双重检查的信号量锁从根本上解决了缓存击穿问题,缓存失效的瞬间仅单个请求会触发Token刷新,其余请求等待锁释放后直接读取已写入的新Token,不会触发第三方的多Token作废机制
  • 缓存过期时间与第三方Token实际有效期严格绑定,预留少量提前量避免边界场景,既不会提前替换未过期的Token,也不会使用已失效的旧Token
  • 逻辑与控制器完全解耦,所有需要调用第三方API的接口仅需注入IThirdPartyAuthService即可获取有效Token,无需重复写Token获取逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.26 11:27:04