chore: 升级示例项目为基于 .NET 6.0 的实现

This commit is contained in:
Fu Diwei
2021-12-27 19:39:03 +08:00
parent 15e9042cad
commit b2ce287136
36 changed files with 23 additions and 19 deletions

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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; }
}
}

View File

@@ -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();
}
}
}