docs: 提供微信公众平台模块的示例项目

This commit is contained in:
Fu Diwei 2021-07-27 18:37:11 +08:00
parent 0fd94ea541
commit b62867de8d
18 changed files with 616 additions and 7 deletions

View File

@ -18,7 +18,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Work", "src\SKIT.FlurlHttpClient.Wechat.Work\SKIT.FlurlHttpClient.Wechat.Work.csproj", "{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.Ads", "src\SKIT.FlurlHttpClient.Wechat.Ads\SKIT.FlurlHttpClient.Wechat.Ads.csproj", "{7F155EFB-152F-4798-9984-99102B21D2F8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads", "src\SKIT.FlurlHttpClient.Wechat.Ads\SKIT.FlurlHttpClient.Wechat.Ads.csproj", "{7F155EFB-152F-4798-9984-99102B21D2F8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C95AF531-CF44-44AA-AC90-F4DF9F941674}"
EndProject
@ -32,7 +32,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Work.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Work.UnitTests\SKIT.FlurlHttpClient.Wechat.Work.UnitTests.csproj", "{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.Ads.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests.csproj", "{561E0BFB-7817-41FE-BAF2-D78817679AC1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests.csproj", "{561E0BFB-7817-41FE-BAF2-D78817679AC1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{35C901ED-C234-4A91-9561-AD89B3BB788D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5", "samples\SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5\SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.csproj", "{D1B321C9-3004-4645-A78D-A85C152062FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -56,6 +60,10 @@ Global
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Release|Any CPU.Build.0 = Release|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Release|Any CPU.Build.0 = Release|Any CPU
{A8453835-4EE8-4FD4-9766-9C0DCB54CDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8453835-4EE8-4FD4-9766-9C0DCB54CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8453835-4EE8-4FD4-9766-9C0DCB54CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -72,14 +80,14 @@ Global
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Release|Any CPU.Build.0 = Release|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F155EFB-152F-4798-9984-99102B21D2F8}.Release|Any CPU.Build.0 = Release|Any CPU
{561E0BFB-7817-41FE-BAF2-D78817679AC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{561E0BFB-7817-41FE-BAF2-D78817679AC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{561E0BFB-7817-41FE-BAF2-D78817679AC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{561E0BFB-7817-41FE-BAF2-D78817679AC1}.Release|Any CPU.Build.0 = Release|Any CPU
{D1B321C9-3004-4645-A78D-A85C152062FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1B321C9-3004-4645-A78D-A85C152062FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1B321C9-3004-4645-A78D-A85C152062FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1B321C9-3004-4645-A78D-A85C152062FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -89,13 +97,14 @@ Global
{082C1F69-7932-473F-A700-49584371BE8C} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{7F155EFB-152F-4798-9984-99102B21D2F8} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{4C48D9D5-1D7F-4616-A05D-256555C310FC} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
{A8453835-4EE8-4FD4-9766-9C0DCB54CDB3} = {4C48D9D5-1D7F-4616-A05D-256555C310FC}
{0C87A7D9-26EA-4821-AF3F-6D28B3006B24} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
{7F155EFB-152F-4798-9984-99102B21D2F8} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{561E0BFB-7817-41FE-BAF2-D78817679AC1} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
{D1B321C9-3004-4645-A78D-A85C152062FA} = {35C901ED-C234-4A91-9561-AD89B3BB788D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F08ED64E-2517-4B51-A4BE-D33D56CC7B39}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SKIT.FlurlHttpClient.Wechat.Security;
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Controllers
{
[ApiController]
[Route("notify")]
public class WechatNotifyController : ControllerBase
{
private readonly ILogger _logger;
private readonly Options.WechatOptions _wechatOptions;
public WechatNotifyController(
ILoggerFactory loggerFactory,
IOptions<Options.WechatOptions> wechatOptions)
{
_logger = loggerFactory.CreateLogger(GetType());
_wechatOptions = wechatOptions.Value;
}
[HttpGet]
[Route("message-push")]
public IActionResult VerifyMessage(
[FromQuery(Name = "app_id")] string? appId,
[FromQuery(Name = "signature")] string? signature,
[FromQuery(Name = "timestamp")] string? timestamp,
[FromQuery(Name = "nonce")] string? nonce,
[FromQuery(Name = "echostr")] string? echoString)
{
// 验证服务器推送
// 文档https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
var wechatAccount = _wechatOptions.Accounts?.FirstOrDefault(e => e.AppId == appId);
if (wechatAccount == null)
return Content("fail");
ISet<string> set = new SortedSet<string>() { _wechatOptions.CallbackToken, timestamp!, nonce! };
string sign = SHA1Utility.Hash(string.Concat(set));
if (!string.Equals(sign, signature, StringComparison.InvariantCultureIgnoreCase))
return Content("fail");
return Content(echoString);
}
[HttpPost]
[Route("message-push")]
public async Task<IActionResult> ReceiveMessage()
{
// 接收服务器推送
// 文档https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
string content = await reader.ReadToEndAsync();
_logger.LogInformation("接收到微信推送的数据:{0}", content);
return Content("success");
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
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.Controllers
{
[ApiController]
[Route("user")]
public class WechatUserController : ControllerBase
{
private readonly ILogger _logger;
private readonly Services.Repositories.IWechatAccessTokenEntityRepository _wechatAccessTokenEntityRepository;
private readonly Services.HttpClients.IWechatApiHttpClientFactory _wechatApiHttpClientFactory;
public WechatUserController(
ILoggerFactory loggerFactory,
Services.Repositories.IWechatAccessTokenEntityRepository wechatAccessTokenEntityRepository,
Services.HttpClients.IWechatApiHttpClientFactory wechatApiHttpClientFactory)
{
_logger = loggerFactory.CreateLogger(GetType());
_wechatAccessTokenEntityRepository = wechatAccessTokenEntityRepository;
_wechatApiHttpClientFactory = wechatApiHttpClientFactory;
}
[HttpGet]
[Route("info")]
public async Task<IActionResult> GetUserInfoByOpenId(
[FromQuery(Name = "app_id")] string appId,
[FromQuery(Name = "open_id")] string openId)
{
// 获取用户基本信息
// 文档https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
var entity = _wechatAccessTokenEntityRepository.FirstOrDefault(e => e.AppId == appId);
var client = _wechatApiHttpClientFactory.Create(appId);
var request = new CgibinUserInfoRequest() { AccessToken = entity?.AccessToken, OpenId = openId };
var response = await client.ExecuteCgibinUserInfoAsync(request, HttpContext.RequestAborted);
if (!response.IsSuccessful())
{
_logger.LogWarning(
"获取用户基本信息失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.RawStatus, response.ErrorCode, response.ErrorMessage
);
}
return new JsonResult(response);
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Models
{
public class WechatAccessTokenEntity
{
public string AppId { get; set; } = string.Empty;
public string AccessToken { get; set; } = string.Empty;
public long ExpireTimestamp { get; set; }
public long UpdateTimestamp { get; set; }
public long CreateTimestamp { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5.Options
{
public partial class WechatOptions : IOptions<WechatOptions>
{
WechatOptions IOptions<WechatOptions>.Value => this;
public WechatAccount[] Accounts { get; set; } = Array.Empty<WechatAccount>();
public string CallbackState { get; set; } = string.Empty;
public string CallbackToken { get; set; } = string.Empty;
}
partial class WechatOptions
{
public class WechatAccount
{
public string? GhId { get; set; }
public string AppId { get; set; } = string.Empty;
public string AppSecret { get; set; } = string.Empty;
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5
{
public class Program
{
public static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.Build()
.Run();
}
}
}

View File

@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"SKIT.FlurlHttpClient.Wechat.Api.Demo": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<NullableReferenceTypes>true</NullableReferenceTypes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DistributedLock.Core" Version="1.0.2" />
<PackageReference Include="DistributedLock.FileSystem" Version="1.0.0" />
<PackageReference Include="NMemory" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SKIT.FlurlHttpClient.Wechat.Api\SKIT.FlurlHttpClient.Wechat.Api.csproj" />
</ItemGroup>
</Project>

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,20 @@
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)
{
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;
}
public WechatApiClient Create(string appId)
{
var wechatAccount = _wechatOptions.Accounts?.FirstOrDefault(e => string.Equals(appId, e.AppId));
if (wechatAccount == null)
throw new Exception("未在配置项中找到该 AppId 对应的微信账号。");
FlurlHttp.GlobalSettings.FlurlClientFactory = new DelegatingFlurlClientFactory(_httpClientFactory);
return new WechatApiClient(new WechatApiClientOptions()
{
AppId = wechatAccount.AppId,
AppSecret = wechatAccount.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();
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace SKIT.FlurlHttpClient.Wechat.Api.Sample_Net5
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// 注入配置项
services.AddOptions();
services.Configure<Options.WechatOptions>(Configuration.GetSection(nameof(Options.WechatOptions)));
// 注入分布式锁
services.AddSingleton<Services.DistributedLock.IDistributedLockFactory, Services.DistributedLock.Implements.DistributedLockFactory>();
// 注入仓储类
services.AddSingleton<Services.Repositories.IWechatAccessTokenEntityRepository, Services.Repositories.Implements.WechatAccessTokenEntityRepository>();
// 注入工厂 HTTP 客户端
services.AddHttpClient();
services.AddSingleton<Services.HttpClients.IWechatApiHttpClientFactory, Services.HttpClients.Implements.WechatApiHttpClientFactory>();
// 注入后台任务
services.AddHostedService<Services.BackgroundServices.WechatAccessTokenRefreshingBackgroundService>();
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"WechatOptions": {
"Accounts": [
{
"AppId": "填写 AppId",
"AppSecret": "填写 AppSecret"
}
],
"CallbackState": "SKIT.FlurlHttpClient.Wechat",
"CallbackToken": "填写服务器推送的 Token"
}
}