diff --git a/src/Senparc.Weixin.MP/Senparc.WeixinTests/Senparc.WeixinTests.NetCore3.csproj b/src/Senparc.Weixin.MP/Senparc.WeixinTests/Senparc.WeixinTests.NetCore3.csproj index 348f9c0cf4..d07e8d9893 100644 --- a/src/Senparc.Weixin.MP/Senparc.WeixinTests/Senparc.WeixinTests.NetCore3.csproj +++ b/src/Senparc.Weixin.MP/Senparc.WeixinTests/Senparc.WeixinTests.NetCore3.csproj @@ -29,6 +29,8 @@ + + @@ -37,4 +39,10 @@ + + + Always + + + diff --git a/src/Senparc.Weixin.MP/Senparc.WeixinTests/WeixinRegisterTests.cs b/src/Senparc.Weixin.MP/Senparc.WeixinTests/WeixinRegisterTests.cs index 26d1e5bfdd..b74718614b 100644 --- a/src/Senparc.Weixin.MP/Senparc.WeixinTests/WeixinRegisterTests.cs +++ b/src/Senparc.Weixin.MP/Senparc.WeixinTests/WeixinRegisterTests.cs @@ -1,4 +1,14 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Senparc.CO2NET; +using Senparc.CO2NET.RegisterServices; +using Senparc.Weixin; +using Senparc.Weixin.Cache.Redis; +using Senparc.Weixin.Entities; +using Senparc.Weixin.MP; +using Senparc.Weixin.Work; +using Senparc.Weixin.WxOpen; using System; using System.Collections.Generic; using System.IO; @@ -17,5 +27,111 @@ public void MyTestMethod() Console.WriteLine(pathSeparator); Console.WriteLine(altPathSeparator); } + + [TestMethod] + public void TestRegisterMpAccount() + { + SenparcWeixinSetting senparcWeixinSetting; + SenparcSetting senparcSetting; + IConfiguration config; + + config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json").Build(); + + senparcSetting = new SenparcSetting(); + senparcWeixinSetting = new SenparcWeixinSetting(); + + config.GetSection("SenparcSetting").Bind(senparcSetting); + config.GetSection("SenparcWeixinSetting").Bind(senparcWeixinSetting); + + var services = new ServiceCollection(); + services.AddSenparcGlobalServices(config); + + IRegisterService register = RegisterService.Start(senparcSetting).UseSenparcGlobal(); + + #region Redis配置 + var redisConfigurationStr = senparcSetting.Cache_Redis_Configuration; + Senparc.CO2NET.Cache.Redis.Register.SetConfigurationOption(redisConfigurationStr); + Senparc.CO2NET.Cache.Redis.Register.UseKeyValueRedisNow(); + + // 这里需要同时安装以下两个Nuget包,否则启动报错 + // Senparc.Weixin.Cache.Redis + // Senparc.Weixin.Cache.CsRedis + register.UseSenparcWeixinCacheRedis(); + + #endregion + + + var logFile = Path.Combine(Directory.GetCurrentDirectory(), "Debugger.log"); + var logs = new List(); + logs.Add("********************** Start configuration the senparcSDK **********************"); + logs.Add($" "); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Before the method: UseSenparcWeixin Invoke"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppId: {senparcWeixinSetting.WeixinAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppSecret: {senparcWeixinSetting.WeixinAppSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Token: {senparcWeixinSetting.Token}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} EncodingAESKey: {senparcWeixinSetting.EncodingAESKey}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpId: {senparcWeixinSetting.WeixinCorpId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpSecret: {senparcWeixinSetting.WeixinCorpSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppId: {senparcWeixinSetting.WxOpenAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppSecret: {senparcWeixinSetting.WxOpenAppSecret}"); + + register.UseSenparcWeixin(senparcWeixinSetting, senparcSetting); + + logs.Add($" "); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} After the method: UseSenparcWeixin Invoke & Before the method: RegisterMpAccount Invoke"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppId: {senparcWeixinSetting.WeixinAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppSecret: {senparcWeixinSetting.WeixinAppSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Token: {senparcWeixinSetting.Token}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} EncodingAESKey: {senparcWeixinSetting.EncodingAESKey}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpId: {senparcWeixinSetting.WeixinCorpId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpSecret: {senparcWeixinSetting.WeixinCorpSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppId: {senparcWeixinSetting.WxOpenAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppSecret: {senparcWeixinSetting.WxOpenAppSecret}"); + + register.RegisterMpAccount(senparcWeixinSetting.MpSetting); // 公众号账号配置 + + logs.Add($" "); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} After the method: RegisterMpAccount Invoke & Before the method: RegisterWorkAccount"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppId: {senparcWeixinSetting.WeixinAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppSecret: {senparcWeixinSetting.WeixinAppSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Token: {senparcWeixinSetting.Token}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} EncodingAESKey: {senparcWeixinSetting.EncodingAESKey}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpId: {senparcWeixinSetting.WeixinCorpId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpSecret: {senparcWeixinSetting.WeixinCorpSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppId: {senparcWeixinSetting.WxOpenAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppSecret: {senparcWeixinSetting.WxOpenAppSecret}"); + + register.RegisterWorkAccount(senparcWeixinSetting.WorkSetting); // 企业微信账号配置 + + logs.Add($" "); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} After the method: RegisterWorkAccount Invoke & Before the method: RegisterWxOpenAccount"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppId: {senparcWeixinSetting.WeixinAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppSecret: {senparcWeixinSetting.WeixinAppSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Token: {senparcWeixinSetting.Token}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} EncodingAESKey: {senparcWeixinSetting.EncodingAESKey}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpId: {senparcWeixinSetting.WeixinCorpId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpSecret: {senparcWeixinSetting.WeixinCorpSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppId: {senparcWeixinSetting.WxOpenAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppSecret: {senparcWeixinSetting.WxOpenAppSecret}"); + + register.RegisterWxOpenAccount(senparcWeixinSetting.WxOpenSetting); // 小程序账号配置 + + logs.Add($" "); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} After the method: RegisterWxOpenAccount Invoke"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppId: {senparcWeixinSetting.WeixinAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinAppSecret: {senparcWeixinSetting.WeixinAppSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} Token: {senparcWeixinSetting.Token}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} EncodingAESKey: {senparcWeixinSetting.EncodingAESKey}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpId: {senparcWeixinSetting.WeixinCorpId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WeixinCorpSecret: {senparcWeixinSetting.WeixinCorpSecret}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppId: {senparcWeixinSetting.WxOpenAppId}"); + logs.Add($"{DateTime.Now:yyyy/MM/dd HH:mm:ss fffff} WxOpenAppSecret: {senparcWeixinSetting.WxOpenAppSecret}"); + logs.Add($" "); + logs.Add("********************** End configuration the senparcSDK **********************"); + + File.AppendAllLines(logFile, logs, Encoding.UTF8); + } } } diff --git a/src/Senparc.Weixin.MP/Senparc.WeixinTests/appsettings.json b/src/Senparc.Weixin.MP/Senparc.WeixinTests/appsettings.json new file mode 100644 index 0000000000..ffee21c47c --- /dev/null +++ b/src/Senparc.Weixin.MP/Senparc.WeixinTests/appsettings.json @@ -0,0 +1,29 @@ +{ + //CO2NET + "SenparcSetting": { + //Ϊ CO2NET SenparcSetting ȫã޸ keyɾκ + "IsDebug": true, + "DefaultCacheNamespace": "DefaultCache", + //ֲʽ + "Cache_Redis_Configuration": "localhost:6379,password=123456,defaultDatabase=3" + //"Cache_Redis_Configuration": "#{Cache_Redis_Configuration}#", //Redis + //"Cache_Redis_Configuration": "localhost:6379",// + //"Cache_Redis_Configuration": "localhost:6379,password=senparc,connectTimeout=1000,connectRetry=2,syncTimeout=10000,defaultDatabase=3",//뼰 + //"Cache_Memcached_Configuration": "#{Cache_Memcached_Configuration}#", //Memcached + //"SenparcUnionAgentKey": "#{SenparcUnionAgentKey}#" //SenparcUnionAgentKey + }, + "SenparcWeixinSetting": { + "IsDebug": true, + "WeixinAppId": "weixinappid-1", + "WeixinAppSecret": "weixinappsecret-1", + "WxOpenAppId": "WxOpenAppId-1", + "WxOpenAppSecret": "WxOpenAppSecret-1", + "WxOpenToken": "#{WxOpenToken}#", + "WxOpenEncodingAESKey": "#{WxOpenEncodingAESKey}#", + "WeixinCorpId": "WeixinCorpId-1", + "WeixinCorpAgentId": "#{WeixinCorpAgentId}#", + "WeixinCorpSecret": "WeixinCorpSecret-1", + "WeixinCorpToken": "WeixinCorpToken-1", + "WeixinCorpEncodingAESKey": "WeixinCorpEncodingAESKey-1" + } +} diff --git a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs new file mode 100644 index 0000000000..e1f5b7a28c --- /dev/null +++ b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Senparc.CO2NET.Extensions; +using Senparc.Weixin.Containers; +using Senparc.Weixin.Entities; +using Senparc.Weixin.Exceptions; +using Senparc.Weixin.MP.CommonAPIs; +using Senparc.Weixin.MP.Entities; +using Senparc.Weixin.Utilities.WeixinUtility; + +namespace Senparc.Weixin.WxOpen.Containers +{ + + public class AccessTokenBag : BaseContainerBag + { + public string WxOpenAppId { get; set; } + public string WxOpenAppSecret { get; set; } + public DateTimeOffset AccessTokenExpireTime { get; set; } + public AccessTokenResult AccessTokenResult { get; set; } + } + + public class AccessTokenContainer : BaseContainer + { + const string LockResourceName = "WxOpen.AccessTokenContainer"; + + #region 同步方法 + + /// + /// 注册应用凭证信息,此操作只是注册,不会马上获取Token,并将清空之前的Token + /// + /// 微信小程序后台的【开发】>【基本配置】中的“AppID(应用ID)” + /// 微信小程序后台的【开发】>【基本配置】中的“AppSecret(应用密钥)” + /// 标记AccessToken名称(如微信小程序名称),帮助管理员识别。当 name 不为 null 和 空值时,本次注册内容将会被记录到 Senparc.Weixin.Config.SenparcWeixinSetting.Items[name] 中,方便取用。 + [Obsolete("请使用 RegisterAsync() 方法")] + public static void Register(string wxOpenAppId, string wxOpenAppSecret, string name = null) + { + var task = RegisterAsync(wxOpenAppId, wxOpenAppSecret, name); + Task.WaitAll(new[] { task }, 10000); + //Task.Factory.StartNew(() => + //{ + // RegisterAsync(wxOpenAppId, wxOpenAppSecret, name).ConfigureAwait(false); + //}).ConfigureAwait(false); + } + + #region AccessToken + + /// + /// 使用完整的应用凭证获取Token,如果不存在将自动注册 + /// + /// + /// + /// + /// + public static string TryGetAccessToken(string wxOpenAppId, string wxOpenAppSecret, bool getNewToken = false) + { + if (!CheckRegistered(wxOpenAppId) || getNewToken) + { + Register(wxOpenAppId, wxOpenAppSecret); + } + return GetAccessToken(wxOpenAppId, getNewToken); + } + + /// + /// 获取可用Token + /// + /// + /// 是否强制重新获取新的Token + /// + public static string GetAccessToken(string wxOpenAppId, bool getNewToken = false) + { + return GetAccessTokenResult(wxOpenAppId, getNewToken).access_token; + } + + /// + /// 获取可用AccessTokenResult对象 + /// + /// + /// 是否强制重新获取新的Token + /// + public static AccessTokenResult GetAccessTokenResult(string wxOpenAppId, bool getNewToken = false) + { + if (!CheckRegistered(wxOpenAppId)) + { + throw new UnRegisterAppIdException(wxOpenAppId, string.Format("此wxOpenAppId({0})尚未注册,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!", wxOpenAppId)); + } + + var accessTokenBag = TryGetItem(wxOpenAppId); + + using (Cache.BeginCacheLock(LockResourceName, wxOpenAppId))//同步锁 + { + if (getNewToken || accessTokenBag.AccessTokenExpireTime <= SystemTime.Now) + { + //已过期,重新获取 + accessTokenBag.AccessTokenResult = CommonApi.GetToken(accessTokenBag.WxOpenAppId, accessTokenBag.WxOpenAppSecret); + accessTokenBag.AccessTokenExpireTime = ApiUtility.GetExpireTime(accessTokenBag.AccessTokenResult.expires_in); + Update(accessTokenBag, null);//更新到缓存 + } + } + return accessTokenBag.AccessTokenResult; + } + + #endregion + + #endregion + + #region 异步方法 + + /// + /// 【异步方法】注册应用凭证信息,此操作只是注册,不会马上获取Token,并将清空之前的Token + /// + /// 微信小程序后台的【开发】>【基本配置】中的N“AppID(应用ID)” + /// 微信小程序后台的【开发】>【基本配置】中的“AppSecret(应用密钥)” + /// 标记AccessToken名称(如微信小程序名称),帮助管理员识别。当 name 不为 null 和 空值时,本次注册内容将会被记录到 Senparc.Weixin.Config.SenparcWeixinSetting.Items[name] 中,方便取用。 + public static async Task RegisterAsync(string wxOpenAppId, string wxOpenAppSecret, string name = null) + { + //记录注册信息,RegisterFunc委托内的过程会在缓存丢失之后自动重试 + RegisterFuncCollection[wxOpenAppId] = async () => + { + //using (FlushCache.CreateInstance()) + //{ + var bag = new AccessTokenBag() + { + //Key = wxOpenAppId, + Name = name, + WxOpenAppId = wxOpenAppId, + WxOpenAppSecret = wxOpenAppSecret, + AccessTokenExpireTime = DateTimeOffset.MinValue, + AccessTokenResult = new AccessTokenResult() + }; + await UpdateAsync(wxOpenAppId, bag, null).ConfigureAwait(false);//第一次添加,此处已经立即更新 + return bag; + //} + }; + + var registerTask = RegisterFuncCollection[wxOpenAppId](); + + if (!name.IsNullOrEmpty()) + { + Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WxOpenAppId = wxOpenAppId; + Senparc.Weixin.Config.SenparcWeixinSetting.Items[name].WxOpenAppSecret = wxOpenAppSecret; + } + + await Task.WhenAll(new[] { registerTask });//等待所有任务完成 + } + + + #region AccessToken + + /// + /// 【异步方法】使用完整的应用凭证获取Token,如果不存在将自动注册 + /// + /// + /// + /// + /// + public static async Task TryGetAccessTokenAsync(string wxOpenAppId, string wxOpenAppSecret, bool getNewToken = false) + { + if (!await CheckRegisteredAsync(wxOpenAppId).ConfigureAwait(false) || getNewToken) + { + await RegisterAsync(wxOpenAppId, wxOpenAppSecret).ConfigureAwait(false); + } + return await GetAccessTokenAsync(wxOpenAppId, getNewToken).ConfigureAwait(false); + } + + /// + /// 【异步方法】获取可用Token + /// + /// + /// 是否强制重新获取新的Token + /// + public static async Task GetAccessTokenAsync(string wxOpenAppId, bool getNewToken = false) + { + var result = await GetAccessTokenResultAsync(wxOpenAppId, getNewToken).ConfigureAwait(false); + return result.access_token; + } + + /// + /// 获取可用AccessTokenResult对象 + /// + /// + /// 是否强制重新获取新的Token + /// + public static async Task GetAccessTokenResultAsync(string wxOpenAppId, bool getNewToken = false) + { + if (!await CheckRegisteredAsync(wxOpenAppId).ConfigureAwait(false)) + { + throw new UnRegisterAppIdException(wxOpenAppId, string.Format("此wxOpenAppId({0})尚未注册,请先使用AccessTokenContainer.Register完成注册(全局执行一次即可)!", wxOpenAppId)); + } + + var accessTokenBag = await TryGetItemAsync(wxOpenAppId).ConfigureAwait(false); + + using (await Cache.BeginCacheLockAsync(LockResourceName, wxOpenAppId).ConfigureAwait(false))//同步锁 + { + if (getNewToken || accessTokenBag.AccessTokenExpireTime <= SystemTime.Now) + { + //已过期,重新获取 + var accessTokenResult = await CommonApi.GetTokenAsync(accessTokenBag.WxOpenAppId, accessTokenBag.WxOpenAppSecret).ConfigureAwait(false); + accessTokenBag.AccessTokenResult = accessTokenResult; + accessTokenBag.AccessTokenExpireTime = ApiUtility.GetExpireTime(accessTokenBag.AccessTokenResult.expires_in); + await UpdateAsync(accessTokenBag, null).ConfigureAwait(false);//更新到缓存 + } + } + return accessTokenBag.AccessTokenResult; + } + + + #endregion + + + #endregion + } +} diff --git a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Register.cs b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Register.cs index 4f35015e60..aa1bc902cc 100644 --- a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Register.cs +++ b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Register.cs @@ -38,8 +38,8 @@ and limitations under the License. using Senparc.Weixin.Exceptions; using Senparc.CO2NET.RegisterServices; using System; -using Senparc.Weixin.MP.Containers; using Senparc.Weixin.Entities; +using Senparc.Weixin.WxOpen.Containers; namespace Senparc.Weixin.WxOpen {