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
{