mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-04-05 08:37:22 +08:00
docs: 提供微信公众平台模块的示例项目
This commit is contained in:
parent
0fd94ea541
commit
b62867de8d
@ -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}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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,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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user