mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-09-20 02:29:40 +08:00
chore: 升级示例项目为基于 .NET 6.0 的实现
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SKIT.FlurlHttpClient.Wechat.Api;
|
||||
using SKIT.FlurlHttpClient.Wechat.Api.Models;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.BackgroundServices
|
||||
{
|
||||
class WechatAccessTokenRefreshingBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Options.WechatOptions _wechatOptions;
|
||||
private readonly DistributedLock.IDistributedLockFactory _distributedLockFactory;
|
||||
private readonly HttpClients.IWechatApiHttpClientFactory _wechatApiHttpClientFactory;
|
||||
private readonly Repositories.IWechatAccessTokenEntityRepository _wechatAccessTokenEntityRepository;
|
||||
|
||||
public WechatAccessTokenRefreshingBackgroundService(
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<Options.WechatOptions> wechatOptions,
|
||||
DistributedLock.IDistributedLockFactory distributedLockFactory,
|
||||
HttpClients.IWechatApiHttpClientFactory wechatApiHttpClientFactory,
|
||||
Repositories.IWechatAccessTokenEntityRepository wechatAccessTokenEntityRepository)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(GetType());
|
||||
_wechatOptions = wechatOptions.Value;
|
||||
_distributedLockFactory = distributedLockFactory;
|
||||
_wechatApiHttpClientFactory = wechatApiHttpClientFactory;
|
||||
_wechatAccessTokenEntityRepository = wechatAccessTokenEntityRepository;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
IList<Task> tasks = new List<Task>();
|
||||
foreach (var wechatAccount in _wechatOptions.Accounts)
|
||||
{
|
||||
Task task = TryRefreshWechatAccessTokenAsync(wechatAccount.AppId, stoppingToken);
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
await Task.Delay(1 * 60 * 1000); // 每隔 1 分钟轮询刷新
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryRefreshWechatAccessTokenAsync(string appId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(appId))
|
||||
return; // 无效参数
|
||||
|
||||
var entity = _wechatAccessTokenEntityRepository.FirstOrDefault(e => e.AppId == appId);
|
||||
if (entity?.ExpireTimestamp > DateTimeOffset.Now.ToUnixTimeSeconds())
|
||||
return; // AccessToken 未过期
|
||||
|
||||
var locker = _distributedLockFactory.Create("accessToken:" + appId);
|
||||
using var lockHandler = await locker.TryAcquireAsync(TimeSpan.FromSeconds(15), cancellationToken);
|
||||
if (lockHandler == null)
|
||||
return; // 未取得锁
|
||||
|
||||
var client = _wechatApiHttpClientFactory.Create(appId);
|
||||
var request = new CgibinTokenRequest();
|
||||
var response = await client.ExecuteCgibinTokenAsync(request, cancellationToken);
|
||||
if (!response.IsSuccessful())
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"刷新 AppId 为 {0} 微信 AccessToken 失败(状态码:{1},错误代码:{2},错误描述:{3})。",
|
||||
appId, response.RawStatus, response.ErrorCode, response.ErrorMessage
|
||||
);
|
||||
return; // 请求失败
|
||||
}
|
||||
|
||||
long nextExpireTimestamp = DateTimeOffset.Now
|
||||
.AddSeconds(response.ExpiresIn)
|
||||
.AddMinutes(-10)
|
||||
.ToUnixTimeSeconds(); // 提前十分钟过期,以便于系统能及时刷新,防止因在过期临界点时出现问题
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new Models.WechatAccessTokenEntity()
|
||||
{
|
||||
AppId = appId,
|
||||
AccessToken = response.AccessToken,
|
||||
ExpireTimestamp = nextExpireTimestamp
|
||||
};
|
||||
_wechatAccessTokenEntityRepository.Insert(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.AccessToken = response.AccessToken;
|
||||
entity.ExpireTimestamp = nextExpireTimestamp;
|
||||
_wechatAccessTokenEntityRepository.Update(entity);
|
||||
}
|
||||
|
||||
_logger.LogInformation("刷新 AppId 为 {0} 的微信 AccessToken 成功。", appId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Medallion.Threading;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.DistributedLock
|
||||
{
|
||||
public interface IDistributedLockFactory
|
||||
{
|
||||
IDistributedLock Create(string lockName);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Medallion.Threading;
|
||||
using Medallion.Threading.FileSystem;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.DistributedLock.Implements
|
||||
{
|
||||
class DistributedLockFactory : IDistributedLockFactory
|
||||
{
|
||||
private readonly DirectoryInfo _lockFileDirectory = new DirectoryInfo(Environment.CurrentDirectory);
|
||||
|
||||
public IDistributedLock Create(string lockName)
|
||||
{
|
||||
// NOTICE: 单机演示基于文件实现分布式锁,生产项目请替换成其他实现
|
||||
return new FileDistributedLock(_lockFileDirectory, lockName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.HttpClients
|
||||
{
|
||||
public interface IWechatApiHttpClientFactory
|
||||
{
|
||||
WechatApiClient Create(string appId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.HttpClients.Implements
|
||||
{
|
||||
partial class WechatApiHttpClientFactory : IWechatApiHttpClientFactory
|
||||
{
|
||||
private readonly System.Net.Http.IHttpClientFactory _httpClientFactory;
|
||||
private readonly Options.WechatOptions _wechatOptions;
|
||||
|
||||
public WechatApiHttpClientFactory(
|
||||
System.Net.Http.IHttpClientFactory httpClientFactory,
|
||||
IOptions<Options.WechatOptions> wechatOptions)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_wechatOptions = wechatOptions.Value;
|
||||
|
||||
FlurlHttp.GlobalSettings.FlurlClientFactory = new DelegatingFlurlClientFactory(_httpClientFactory);
|
||||
}
|
||||
|
||||
public WechatApiClient Create(string appId)
|
||||
{
|
||||
var wechatAccountOptions = _wechatOptions.Accounts?.FirstOrDefault(e => string.Equals(appId, e.AppId));
|
||||
if (wechatAccountOptions == null)
|
||||
throw new Exception("未在配置项中找到该 AppId 对应的微信账号。");
|
||||
|
||||
return new WechatApiClient(new WechatApiClientOptions()
|
||||
{
|
||||
AppId = wechatAccountOptions.AppId,
|
||||
AppSecret = wechatAccountOptions.AppSecret
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
partial class WechatApiHttpClientFactory
|
||||
{
|
||||
internal class DelegatingFlurlClientFactory : IFlurlClientFactory
|
||||
{
|
||||
private readonly System.Net.Http.IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public DelegatingFlurlClientFactory(System.Net.Http.IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
}
|
||||
|
||||
public IFlurlClient Get(Url url)
|
||||
{
|
||||
return new FlurlClient(_httpClientFactory.CreateClient(url.ToUri().Host));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.Repositories
|
||||
{
|
||||
public interface IWechatAccessTokenEntityRepository : IEnumerable<Models.WechatAccessTokenEntity>
|
||||
{
|
||||
void Insert(Models.WechatAccessTokenEntity entity);
|
||||
|
||||
void Update(Models.WechatAccessTokenEntity entity);
|
||||
|
||||
void Delete(Models.WechatAccessTokenEntity entity);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NMemory;
|
||||
using NMemory.Tables;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.Repositories.Implements
|
||||
{
|
||||
internal class GlobalDatabase
|
||||
{
|
||||
static GlobalDatabase()
|
||||
{
|
||||
Database db = new Database();
|
||||
|
||||
TableWechatAccessTokenEntity = db.Tables.Create<Models.WechatAccessTokenEntity, string>(e => e.AppId);
|
||||
}
|
||||
|
||||
public static Table<Models.WechatAccessTokenEntity, string> TableWechatAccessTokenEntity { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Services.Repositories.Implements
|
||||
{
|
||||
public class WechatAccessTokenEntityRepository : IWechatAccessTokenEntityRepository
|
||||
{
|
||||
public void Insert(Models.WechatAccessTokenEntity entity)
|
||||
{
|
||||
entity.CreateTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
entity.UpdateTimestamp = entity.CreateTimestamp;
|
||||
GlobalDatabase.TableWechatAccessTokenEntity.Insert(entity);
|
||||
}
|
||||
|
||||
public void Update(Models.WechatAccessTokenEntity entity)
|
||||
{
|
||||
entity.UpdateTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
GlobalDatabase.TableWechatAccessTokenEntity.Update(entity);
|
||||
}
|
||||
|
||||
public void Delete(Models.WechatAccessTokenEntity entity)
|
||||
{
|
||||
GlobalDatabase.TableWechatAccessTokenEntity.Delete(entity);
|
||||
}
|
||||
|
||||
IEnumerator<Models.WechatAccessTokenEntity> IEnumerable<Models.WechatAccessTokenEntity>.GetEnumerator()
|
||||
{
|
||||
return GlobalDatabase.TableWechatAccessTokenEntity.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GlobalDatabase.TableWechatAccessTokenEntity.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user