using Hncore.Infrastructure.Common; using Hncore.Infrastructure.Extension; using Hncore.Infrastructure.Serializer; using Hncore.Pass.MsgCenter.Constant; using Hncore.Pass.MsgCenter.Util; using Hncore.Pass.MsgCenter.WxOpen.Response; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Web; namespace Hncore.Wx.Open { public class WxOpenApi { private static readonly AsyncLock _mutex1 = new AsyncLock(); private static readonly AsyncLock _mutex2 = new AsyncLock(); private static readonly AsyncLock _mutex4 = new AsyncLock(); private static Dictionary WxAppMap = new Dictionary(); private static IHttpClientFactory _HttpClientFactory; public static void Init(IConfiguration config, IHttpClientFactory HttpClientFactory) { _HttpClientFactory = HttpClientFactory; var key = config[$"WxApps:AppID"]; WxAppMap[key] = config[$"WxApps:AppSecret"]; //var WxMpApps = config.GetSection("WxApps"); //var nodes = WxMpApps.GetChildren(); //for (var i = 0; i < nodes.Count(); i++) //{ // var key = config[$"WxApps:{i}:AppID"]; // var value = config[$"WxApps:{i}:AppSecret"]; // if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) // WxAppMap[key] = value; //} } public static string GetWxAppSecret(string appid) { if (WxAppMap.ContainsKey(appid)) return WxAppMap[appid]; else return ""; } private static HttpClient GetHttpClient() { return _HttpClientFactory.CreateClient("WxOpen"); } public static async Task GetComponentTicket() { return await RedisHelper.GetAsync(ConstantConfig.Redis_Psipwechat_Ticket_Key); } public static async Task SetComponentTicket(string ticket) { return await RedisHelper.SetAsync(ConstantConfig.Redis_Psipwechat_Ticket_Key, ticket); } /// /// 开放平台:得到开放平台的accessToken /// /// public static async Task GetComponentAccessToken() { var component_appid = HostContext.Configuration["WxOpen:AppID"]; var key =string.Format(ConstantConfig.Redis_ComponentAccessToken_Key, component_appid); var tokenJson = await RedisHelper.GetAsync(key); var token = tokenJson.FromJsonToOrDefault(); if (token == null || token.need_to_refresh_token) { using (await _mutex1.LockAsync()) { tokenJson = await RedisHelper.GetAsync(key); token = tokenJson.FromJsonToOrDefault(); if (token != null && !token.need_to_refresh_token) { return token.component_access_token; } //重新去微信获取 var ticket =await GetComponentTicket(); token = await get_component_access_token(ticket); if (token.errcode == 0) { await RedisHelper.SetAsync(key, token.ToJson(), token.expires_in); return token.component_access_token; } else { return ""; } } } return token.component_access_token; } /// /// 开放平台:得到公众平台的AccessToken /// /// public static async Task GetAuthorizerAccessToken(string authorizer_appid) { var key = string.Format(ConstantConfig.Redis_AuthorizerAccessToken_Key, authorizer_appid); var refresh_key = string.Format(ConstantConfig.Redis_Refresh_AuthorizerAccessToken_Key, authorizer_appid); var tokenJson = await RedisHelper.GetAsync(key); var token = tokenJson.FromJsonToOrDefault(); if (token == null || token.need_to_refresh_token) { using (await _mutex2.LockAsync()) { tokenJson = await RedisHelper.GetAsync(key); token = tokenJson.FromJsonToOrDefault(); if (token != null && !token.need_to_refresh_token) { return token.authorizer_access_token; } var authorizer_refresh_token = await RedisHelper.GetAsync(refresh_key); var component_access_token = await GetComponentAccessToken(); token = await get_authorizer_access_token_by_refresh(authorizer_appid, authorizer_refresh_token, component_access_token); if (token.errcode == 0) { await RedisHelper.SetAsync(key, token.ToJson(), token.expires_in); await RedisHelper.SetAsync(refresh_key, token.authorizer_refresh_token,60*60*24*30); return token.authorizer_access_token; } else { return ""; } } } return token.authorizer_access_token; } public static async Task ReFreshAccessToken(string authorizer_appid, string component_access_token) { var key = string.Format(ConstantConfig.Redis_AuthorizerAccessToken_Key, authorizer_appid); var refresh_key = string.Format(ConstantConfig.Redis_Refresh_AuthorizerAccessToken_Key, authorizer_appid); var authorizer_refresh_token = await RedisHelper.GetAsync(refresh_key); using (await _mutex4.LockAsync()) { var token = await get_authorizer_access_token_by_refresh(authorizer_appid, authorizer_refresh_token, component_access_token); if (token.errcode == 0) { await RedisHelper.SetAsync(key, token.ToJson(), token.expires_in); await RedisHelper.SetAsync(refresh_key, token.authorizer_refresh_token,60 * 60 * 24 * 30); } } } /// ///开放平台: 通过回调返回授权码的方式获得公众号的accesstoken /// /// /// public static async Task GetAuthorizerAccessTokenByCode(string authorization_code,Func> callback) { var component_access_token = await GetComponentAccessToken(); var ret = await get_authorizer_access_token_by_callback(authorization_code, component_access_token); var authorizer_appid = ret.authorization_info.authorizer_appid; //存access_token的key var key = string.Format(ConstantConfig.Redis_AuthorizerAccessToken_Key, authorizer_appid); //存refresh_access_token的key var refresh_key = string.Format(ConstantConfig.Redis_Refresh_AuthorizerAccessToken_Key, authorizer_appid); var info = new GetAuthorizerAccessTokenResponse() { authorizer_access_token = ret.authorization_info.authorizer_access_token, authorizer_refresh_token = ret.authorization_info.authorizer_refresh_token, expires_in = ret.authorization_info.expires_in, create_from = DateTime.Now.TimestampFrom19700101() }; await RedisHelper.SetAsync(key, info.ToJson(), info.expires_in); await RedisHelper.SetAsync(refresh_key, info.authorizer_refresh_token); if (callback != null) { return await callback(ret.authorization_info); } return info.authorizer_access_token; } /// ///开放平台: 得到开放平台的预授权码 /// /// public static async Task GetPreAuthCode() { var component_access_token = await GetComponentAccessToken(); var ret = await get_pre_auth_Code(component_access_token); return ret.pre_auth_code; } /// ///开放平台: 代公众号获取用户在公众号的OpenId,以及AccessToken 信息 /// /// /// 结果对象,里边包含了授权令牌信息 public static async Task GetAuthorizerClientUserOpenIdInfo(string appid, string code) { var component_access_token = await GetComponentAccessToken(); var ret = await get_authorizer_user_openIdifno(appid, code, component_access_token); return ret; } /// /// 开放平台:为公众平台授权的url /// /// public static async Task GetAuthorizationUrl(string callback_url) { var appID = HostContext.Configuration["WxOpen:AppID"]; ; var pre_auth_code = await GetPreAuthCode(); var baseUrl = HostContext.Configuration["BaseInfoUrl"]; callback_url = $"{baseUrl}/{callback_url}";//回调地址 callback_url = HttpUtility.UrlEncode(callback_url); //开放平台授权地址 var url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?" + $"component_appid={appID}" + $"&pre_auth_code={pre_auth_code}" + $"&redirect_uri={callback_url}"; return url; } private static readonly AsyncLock _mutex3 = new AsyncLock(); /// /// 【公众平台、小程序】:得到公众平台、小程序的accessToken /// /// public static async Task GetAccessToken(string appid, string secret) { var key = string.Format(ConstantConfig.Redis_MP_AccessToken_Key, appid); var tokenJson = await RedisHelper.GetAsync(key); var token = tokenJson.FromJsonToOrDefault(); if (token == null || token.need_to_refresh_token) { using (await _mutex3.LockAsync()) { tokenJson = await RedisHelper.GetAsync(key); token = tokenJson.FromJsonToOrDefault(); if (token != null && !token.need_to_refresh_token) { return token.access_token; } token = await get_access_token(appid, secret); if (token.errcode == 0) { await RedisHelper.SetAsync(key, token.ToJson(), token.expires_in); return token.access_token; } return ""; } } return token.access_token; } /// /// 公众平台,通过code换取网页授权access_token /// /// /// /// public static async Task GetWebAccessToken(string appid, string code) { var secret = GetWxAppSecret(appid); if (string.IsNullOrWhiteSpace(secret)) { LogHelper.Error("GetWebAccessToken", $"appid={appid} 没有配置"); return null; } return await get_web_access_token(appid, secret, code); } /// /// 通过access_token和openid拉取用户信息 /// /// /// /// public static async Task GetUserinfoByWebAccessToken(string access_token, string openid) { var key = string.Format(ConstantConfig.Redis_MP_GetUserinfoByWebAccessToken_Key, openid); var userInfo = await RedisHelper.GetAsync(key); if (userInfo == null) { userInfo = await get_userinfo_by_web_access_token(access_token, openid); if (userInfo.errcode == 0) await RedisHelper.SetAsync(key, userInfo.ToJson(), 2 * 60 * 60); } return userInfo; } /// /// 开放平台获得获取用户基本信息(UnionID机制) /// /// /// /// public static async Task GetUserUnionIDinfo(string appid, string openid) { var key = string.Format(ConstantConfig.Redis_MP_GetUserUnionIDInfo_Key, openid); var userInfo = await RedisHelper.GetAsync(key); if (userInfo == null) { var access_token = await GetAuthorizerAccessToken(appid); userInfo = await get_user_openid_info(access_token, openid); if (userInfo.errcode == 0) await RedisHelper.SetAsync(key, userInfo.ToJson(), 2 * 60 * 60); } return userInfo; } /// /// 公众平台获得获取用户基本信息() /// /// /// /// public static async Task GetUserinfoByOpenId(string appid, string openid) { var key = string.Format(ConstantConfig.Redis_MP_GetUserOpenIdInfo_Key, openid); var userInfo = await RedisHelper.GetAsync(key); if (userInfo == null) { var access_token = await GetAccessToken(appid,GetWxAppSecret(appid)); userInfo = await get_user_openid_info(access_token, openid); if (userInfo.errcode == 0) await RedisHelper.SetAsync(key, userInfo.ToJson(), 2 * 60 * 60); } return userInfo; } /// /// 通过开放平台获得公众号的信息 /// /// /// /// public static async Task GetMpInfo(string authorizer_appid) { var key = string.Format(ConstantConfig.Redis_MP_Info_Key, authorizer_appid); var mpInfo = await RedisHelper.GetAsync(key); if (mpInfo == null) { var component_token = await GetComponentAccessToken(); mpInfo = await get_mp_info(component_token, authorizer_appid); if (mpInfo.errcode == 0) await RedisHelper.SetAsync(key, mpInfo.ToJson(), 1 * 60 * 60); } return mpInfo.authorizer_info; } /// /// 为公众平台创建菜单 /// /// /// public static async Task CreateMPMenu(string access_token, object menu) { var ret = await create_mp_menu(access_token, menu); return ret.errcode == 0; } /// /// 为公众平台粉丝添加标签 /// /// /// public static async Task AddTag(string appid, List openIds, string tagId) { var access_token = await GetAuthorizerAccessToken(appid); await add_tag(access_token, openIds, int.Parse(tagId)); } /// /// 得到公众平台所有标签 /// /// /// public static async Task GetTags(string appid) { var access_token = await GetAuthorizerAccessToken(appid); return await get_tags(access_token); } /// /// 得到公众平台jsapi ticket /// /// /// public static async Task GetJsTicket(string authorizer_appid) { var key = string.Format(ConstantConfig.Redis_MP_Js_Ticket_Key, authorizer_appid); var ticketInfo = await RedisHelper.GetAsync(key); if (ticketInfo == null) { var access_token = await GetAuthorizerAccessToken(authorizer_appid); ticketInfo = await get_jsapi_ticket(access_token); if (ticketInfo.errcode == 0) await RedisHelper.SetAsync(key, ticketInfo.ToJson(), 1 * 60 * 60); } return ticketInfo; } /// /// 为公众平台创建二维码 /// /// /// public static async Task CreatePermanentQrcode(string appid, string scene_str) { var access_token = await GetAuthorizerAccessToken(appid); return await create_permanent_qrcode(access_token, scene_str); } #region 内部微信接口 /// /// 开放平台:得到access_token /// /// /// private static async Task get_component_access_token(string ticket) { string url = "cgi-bin/component/api_component_token"; var reqData = new { component_appid = HostContext.Configuration["WxOpen:AppID"], component_appsecret = HostContext.Configuration["WxOpen:AppSecret"], component_verify_ticket = ticket, }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonTo(); if (ret.errcode > 0) { LogHelper.Error("get_component_access_token", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_component_access_token", respData); return ret; } /// ///开放平台:通过刷新的方式获得公众平台的accesstoken /// /// /// private static async Task get_authorizer_access_token_by_refresh(string authorizer_appid, string authorizer_refresh_token,string component_access_token) { string url = $"cgi-bin/component/api_authorizer_token?component_access_token={component_access_token}"; var reqData = new { component_appid = HostContext.Configuration["WxOpen:AppID"], authorizer_appid, authorizer_refresh_token, }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_authorizer_access_token_by_refresh", $"errcode={ret.errcode},errmsg={ret.errmsg},authorizer_appid={authorizer_appid}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_authorizer_access_token_by_callback", respData); return ret; } /// ///开放平台: 通过授权码回调方式获得公众平台的accesstoken /// /// /// private static async Task get_authorizer_access_token_by_callback(string authorization_code,string component_access_token) { string url = $"cgi-bin/component/api_query_auth?component_access_token={component_access_token}"; var reqData = new { component_appid = HostContext.Configuration["WxOpen:AppID"], authorization_code, }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_authorizer_access_token_by_callback", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_authorizer_access_token_by_callback", respData); return ret; } /// ///开放平台: 获取开放平台预授权代码 用于生成授权url /// private static async Task get_pre_auth_Code(string component_access_token) { string url = $"cgi-bin/component/api_create_preauthcode?component_access_token={component_access_token}"; var reqData = new { component_appid = HostContext.Configuration["WxOpen:AppID"], }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_pre_auth_Code", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_pre_auth_Code", respData); return ret; } /// /// 开放平台:获得公众平台用户openid信息 /// /// /// /// /// private static async Task get_authorizer_user_openIdifno(string appid, string code, string component_access_token) { LogHelper.Debug("get_authorizer_user_openIdifno_params", $"{appid},{code}"); var component_appid = HostContext.Configuration["WxOpen:AppID"]; string url = $"sns/oauth2/component/access_token?appid={appid}&code={code}&grant_type=authorization_code&component_appid={component_appid}&component_access_token={component_access_token}"; LogHelper.Debug("get_authorizer_user_openIdifno-url", url); var resp = await GetHttpClient().GetStringAsync(url); var respData = resp.FromJsonToOrDefault(); LogHelper.Debug("get_authorizer_user_openIdifno", resp); return respData; } /// ///公众平台,小程序: 直接获得公众平台,小程序的accesstoken /// /// /// private static async Task get_access_token(string appid, string secret) { string url = $"cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_mp_access_token", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_mp_access_token", respData); return ret; } /// ///公众平台,通过code换取网页授权access_token ///这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同 ///https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 /// /// /// private static async Task get_web_access_token(string appid, string secret,string code) { string url = $"sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_web_access_token", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } ret.create_from = DateTime.Now.TimestampFrom19700101(); LogHelper.Debug("get_web_access_token", respData); return ret; } /// /// 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。 /// https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 /// /// 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 /// /// private static async Task get_userinfo_by_web_access_token(string access_token, string openid) { string url = $"sns/userinfo?access_token={access_token}&openid={openid}&lang=zh_CN"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_userinfo_by_web_access_token", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("get_userinfo_by_web_access_token", respData); return ret; } private static async Task get_user_openid_info(string access_token, string openid) { string url = $"cgi-bin/user/info?access_token={access_token}&openid={openid}&lang=zh_CN"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_user_openid_info", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("get_user_openid_info", respData); return ret; } /// /// 通过开放平台得到公众号的信息,主要是 (authorizer_info) /// /// /// /// private static async Task get_mp_info(string component_token, string authorizer_appid) { string url = $"cgi-bin/component/api_get_authorizer_info?component_access_token={component_token}"; var reqData = new { authorizer_appid, component_appid = HostContext.Configuration["WxOpen:AppID"], }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_mp_info_resp", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("get_mp_info_resp", respData); return ret; } /// /// 为公众平台创建菜单 /// /// /// /// private static async Task create_mp_menu(string access_token, object menu) { string url = $"cgi-bin/menu/create?access_token={access_token}"; var respData = await GetHttpClient().PostAsJsonGetString(url, menu); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("create_mp_menu", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("create_mp_menu", respData); return ret; } /// /// 为公众号粉丝打标签 /// /// /// /// private static async Task add_tag(string access_token, List openIds,int tagId) { string url = $"cgi-bin/tags/members/batchtagging?access_token={access_token}"; var reqData = new { openid_list = openIds, tagid = tagId }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("add_tag", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("add_tag", respData); return ret; } /// /// 创建公众号二维码 /// /// /// /// private static async Task get_tags(string access_token) { string url = $"cgi-bin/tags/get?access_token={access_token}"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_tags", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("get_tags", respData); return ret; } /// /// 创建公众号二维码 /// /// /// /// private static async Task create_permanent_qrcode(string access_token,string scene_str) { string url = $"cgi-bin/qrcode/create?access_token={access_token}"; //{ "action_name": "QR_LIMIT_STR_SCENE", "action_info": { "scene": { "scene_str": "test"} } } var reqData = new { action_name = "QR_LIMIT_STR_SCENE", action_info = new { scene = new { scene_str } } }; var respData = await GetHttpClient().PostAsJsonGetString(url, reqData); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("create_permanent_qrcode", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("create_permanent_qrcode", respData); return ret; } public static async Task get_jsapi_ticket(string access_token) { string url = $"cgi-bin/ticket/getticket?access_token={access_token}&type=jsapi"; var respData = await GetHttpClient().GetStringAsync(url); var ret = respData.FromJsonToOrDefault(); if (ret.errcode > 0) { LogHelper.Error("get_jsapi_ticket", $"errcode={ret.errcode},errmsg={ret.errmsg}"); } LogHelper.Debug("get_jsapi_ticket", respData); return ret; } #endregion } }