feat: 新增微信小程序模块

This commit is contained in:
橙子
2024-10-19 14:02:29 +08:00
parent 4ae548cc5b
commit 736995c35b
22 changed files with 372 additions and 0 deletions

View File

@@ -182,6 +182,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollect
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.SqlSugarCore", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj", "{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniProgram", "framework\Yi.Framework.WeChat.MiniProgram\Yi.Framework.WeChat.MiniProgram.csproj", "{81CEA2ED-917B-41D8-BE0D-39A785B050C0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -460,6 +462,10 @@ Global
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.Build.0 = Release|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -541,6 +547,7 @@ Global
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IErrorObjct: IHasErrcode, IHasErrmsg
{
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IHasErrcode
{
public int errcode { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Abstract;
public interface IHasErrmsg
{
string errmsg { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
public class AccessTokenResponse
{
public string access_token { get; set; }
public int expires_in { get; set; }
}
public class AccessTokenRequest
{
public string grant_type { get; set; }
public string appid { get; set; }
public string secret { get; set; }
}

View File

@@ -0,0 +1,31 @@
using Yi.Framework.WeChat.MiniProgram.Abstract;
namespace Yi.Framework.WeChat.MiniProgram.HttpModels;
public class Code2SessionResponse: IErrorObjct
{
public string openid { get; set; }
public string session_key { get; set; }
public string unionid { get; set; }
public int errcode { get; set; }
public string errmsg { get; set; }
}
public class Code2SessionRequest
{
public string appid { get; set; }
public string secret { get; set; }
public string js_code { get; set; }
public string grant_type => "authorization_code";
}
public class Code2SessionInput
{
public Code2SessionInput(string js_code)
{
this.js_code=js_code;
}
public string js_code { get; set; }
}

View File

@@ -0,0 +1,13 @@
using Yi.Framework.WeChat.MiniProgram.HttpModels;
namespace Yi.Framework.WeChat.MiniProgram;
public interface IWeChatMiniProgramManager
{
/// <summary>
/// 获取用户openid
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input);
}

View File

@@ -0,0 +1,28 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
namespace Yi.Framework.WeChat.MiniProgram.Token;
internal class CacheMiniProgramToken : DefaultMinProgramToken, IMiniProgramToken
{
private IDistributedCache<string> _cache;
private const string CacheKey = "MiniProgramToken";
public CacheMiniProgramToken(IOptions<WeChatMiniProgramOptions> options, IDistributedCache<string> cache) :
base(options)
{
_cache = cache;
}
public async Task<string> GetTokenAsync()
{
return await _cache.GetOrAddAsync("MiniProgramToken", async () => { return await base.GetTokenAsync(); }, () =>
{
return new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2) - TimeSpan.FromMinutes(1)
};
});
}
}

View File

@@ -0,0 +1,40 @@
using System.Net.Http.Json;
using Microsoft.Extensions.Options;
using Yi.Framework.Core.Extensions;
using Yi.Framework.WeChat.MiniProgram.HttpModels;
namespace Yi.Framework.WeChat.MiniProgram.Token;
internal class DefaultMinProgramToken:IMiniProgramToken
{
private const string Url = "https://api.weixin.qq.com/cgi-bin/token";
private WeChatMiniProgramOptions _options;
public DefaultMinProgramToken(IOptions<WeChatMiniProgramOptions> options)
{
_options = options.Value;
}
public async Task<string> GetTokenAsync()
{
var token = await this.GetAccessToken();
return token.access_token;
}
public async Task<AccessTokenResponse> GetAccessToken()
{
var req = new AccessTokenRequest();
req.appid = _options.AppID;
req.secret = _options.AppSecret;
req.grant_type = "client_credential";
using (HttpClient httpClient = new HttpClient())
{
string queryString = req.ToQueryString();
var builder = new UriBuilder(Url);
builder.Query = queryString;
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadFromJsonAsync<AccessTokenResponse>();
return responseBody;
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.WeChat.MiniProgram.Token;
public interface IMiniProgramToken
{
public Task<string> GetTokenAsync();
}

View File

@@ -0,0 +1,27 @@
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramException: Exception
{
public override string Message
{
get
{
// 加上前缀
return "微信Api异常: " + base.Message;
}
}
public WeChatMiniProgramException()
{
}
public WeChatMiniProgramException(string message)
: base(message)
{
}
public WeChatMiniProgramException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,50 @@
using System.Reflection;
using System.Web;
using Yi.Framework.WeChat.MiniProgram.Abstract;
namespace Yi.Framework.WeChat.MiniProgram;
public static class WeChatMiniProgramExtensions
{
/// <summary>
/// 效验请求是否成功
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static void ValidateSuccess(this IErrorObjct response)
{
if (response.errcode != 0)
{
throw new WeChatMiniProgramException(response.errmsg);
}
}
internal static string ToQueryString<T>(this T obj)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var queryParams = new List<string>();
foreach (var prop in properties)
{
var value = prop.GetValue(obj, null);
if (value != null)
{
// 处理集合
if (value is IEnumerable<object> enumerable)
{
foreach (var item in enumerable)
{
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(item.ToString())}");
}
}
else
{
queryParams.Add($"{HttpUtility.UrlEncode(prop.Name)}={HttpUtility.UrlEncode(value.ToString())}");
}
}
}
return string.Join("&", queryParams);
}
}

View File

@@ -0,0 +1,47 @@
using System.Net.Http.Json;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Core.Extensions;
using Yi.Framework.WeChat.MiniProgram.HttpModels;
using Yi.Framework.WeChat.MiniProgram.Token;
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramManager : IWeChatMiniProgramManager, ISingletonDependency
{
private IMiniProgramToken _weChatToken;
private WeChatMiniProgramOptions _options;
public WeChatMiniProgramManager(IMiniProgramToken weChatToken, IOptions<WeChatMiniProgramOptions> options)
{
_weChatToken = weChatToken;
_options = options.Value;
}
/// <summary>
/// 获取用户openid
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<Code2SessionResponse> Code2SessionAsync(Code2SessionInput input)
{
string url = "https://api.weixin.qq.com/sns/jscode2session";
var req = new Code2SessionRequest();
req.js_code = input.js_code;
req.secret = _options.AppSecret;
req.appid = _options.AppID;
using (HttpClient httpClient = new HttpClient())
{
string queryString = req.ToQueryString();
var builder = new UriBuilder(url);
builder.Query = queryString;
HttpResponseMessage response = await httpClient.GetAsync(builder.ToString());
var responseBody = await response.Content.ReadFromJsonAsync<Code2SessionResponse>();
responseBody.ValidateSuccess();
return responseBody;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Yi.Framework.WeChat.MiniProgram;
public class WeChatMiniProgramOptions
{
/// <summary>
/// AppId
/// </summary>
public string AppID { get; set; }
/// <summary>
/// App密钥
/// </summary>
public string AppSecret { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace Yi.Framework.DigitalCollectibles.Application.Contracts.Dtos.Account;
public class LoginInput
{
/// <summary>
/// 微信小程序code
/// </summary>
public string Code { get; set; }
}

View File

@@ -0,0 +1,13 @@
using Yi.Framework.DigitalCollectibles.Domain.Shared.Enums;
namespace Yi.Framework.DigitalCollectibles.Application.Contracts.Dtos.Account;
public class LoginOutput
{
/// <summary>
/// 后端访问token
/// </summary>
public string? Token { get; set; }
public LoginResultEnum Result{ get; set; }
}

View File

@@ -4,6 +4,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application.Contracts\Yi.Framework.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.DigitalCollectibles.Domain.Shared\Yi.Framework.DigitalCollectibles.Domain.Shared.csproj" />
<ProjectReference Include="..\..\rbac\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,11 +1,13 @@
using Yi.Framework.DigitalCollectibles.Domain.Shared;
using Yi.Framework.Ddd.Application.Contracts;
using Yi.Framework.Rbac.Application.Contracts;
namespace Yi.Framework.DigitalCollectibles.Application.Contracts
{
[DependsOn(
typeof(YiFrameworkDigitalCollectiblesDomainSharedModule),
typeof(YiFrameworkRbacApplicationContractsModule),
typeof(YiFrameworkDddApplicationContractsModule))]
public class YiFrameworkDigitalCollectiblesApplicationContractsModule:AbpModule
{

View File

@@ -0,0 +1,41 @@
using Volo.Abp.Application.Services;
namespace Yi.Framework.DigitalCollectibles.Application.Services.Account;
public class CollectiblesAccountService: ApplicationService
{
/// <summary>
/// 小程序登录
/// </summary>
/// <returns></returns>
public Task PostLoginAsync()
{
throw new NotImplementedException();
//根据code去获取wxid
//判断wxid中是否有对应的userid关系
//果然有直接根据userid返回该用户token
//如果没有,返回结果即可
}
/// <summary>
/// 小程序绑定账号
/// </summary>
/// <returns></returns>
public Task PostBindAsync()
{
throw new NotImplementedException();
//根据code去获取wxid
//校验手机号
//根据手机号查询用户信息
//将wxid和用户user绑定
}
//小程序注册
public Task PostRegisterAsync()
{
throw new NotImplementedException();
//走普通注册流程
//同时再加一个小程序绑定即可
}
}

View File

@@ -4,6 +4,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" />
<ProjectReference Include="..\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.DigitalCollectibles.Domain\Yi.Framework.DigitalCollectibles.Domain.csproj" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Quartz" Version="$(AbpVersion)" />

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.DigitalCollectibles.Domain.Shared.Enums;
public enum LoginResultEnum
{
Error,
Success
}

View File

@@ -114,6 +114,7 @@ public class MiningPoolManager : DomainService
//虽然新增的是一天但是每次刷新是早上10点矿池刷新时还需要清除限制
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
});
return;
}
//已上过锁,并且没有到限制时间,必定失败