最近做微信公眾號開發,涉及到access_token的緩存問題(避免各自的應用都去取access_token,同時解決微信 appid和appsecret的安全問題),在通用權限管理系統底層增加了實現方法:
(access_token默認2小時過期,每取一次,上一次的就自動失效,每天取的次數有限制)
//----------------------------------------------------------------- // All Rights Reserved , Copyright (C) 2016 , Hairihan TECH, Ltd. //----------------------------------------------------------------- using System; using System.Net; using System.Text; using System.Web.Script.Serialization; namespace DotNet.Business.HttpUtilities { using DotNet.Utilities; /// <summary> /// WeChatUtilities /// 微信公共服務,遠程微信調用接口 /// /// 修改記錄 /// /// 2016.11.16 版本:1.0 SongBiao 遠程調用服務。 /// /// <author> /// <name>SongBiao</name> /// <date>2016.11.16</date> /// </author> /// </summary> public class WeChatUtilities { /// <summary> /// 獲取微信AccessToken /// Redis全局緩存,過期自動獲取 /// 對應於公眾號是全局唯一的票據,重復獲取將導致上次獲取的access_token失效 /// </summary> /// <returns></returns> public static string GetAccessToken() { string key = "WXAccessToken"; string accessToken = string.Empty; DateTime expiresAt = DateTime.Now; using (var redisClient = PooledRedisHelper.GetTokenClient()) { AccessTokenResult tokenResult = redisClient.Get<AccessTokenResult>(key); // 不存在或者已過期 if (tokenResult == null || (tokenResult != null && DateTime.Now > tokenResult.expiresAt)) { JavaScriptSerializer js = new JavaScriptSerializer(); string appId = BaseSystemInfo.WeiXinAppId; string appSecret = BaseSystemInfo.WeiXinAppSecret; var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret); using (WebClient wc = new WebClient()) { wc.Proxy = null; wc.Encoding = Encoding.UTF8; string returnText = wc.DownloadString(url); if (returnText.Contains("errcode")) { //可能發生錯誤 //可能發生錯誤 WxJsonResult errorResult = js.Deserialize<WxJsonResult>(returnText); if (errorResult.errcode != 0) { //發生錯誤 throw new Exception(string.Format("微信請求發生錯誤!錯誤代碼:{0},說明:{1}", (int)errorResult.errcode, errorResult.errmsg)); } } tokenResult = js.Deserialize<AccessTokenResult>(returnText); // 添加到緩存中 減少10秒 避免一些問題 expiresAt = DateTime.Now.AddSeconds(tokenResult.expires_in); tokenResult.expiresAt = expiresAt; redisClient.Set(key, tokenResult, expiresAt); NLogHelper.Trace(DateTime.Now + ",微信accessToken過期,重新獲取,下次過期時間:" + expiresAt); } } accessToken = tokenResult.access_token; } return accessToken; } #region 微信公用 /// <summary> /// 微信接口 /// </summary> interface IJsonResult { string errmsg { get; set; } object P2PData { get; set; } } /// <summary> /// 公眾號返回碼(JSON) /// 應該更名為ReturnCode_MP,但為減少項目中的修改,此處依舊用ReturnCode命名 /// </summary> enum ReturnCode { 系統繁忙此時請開發者稍候再試 = -1, 請求成功 = 0, 獲取access_token時AppSecret錯誤或者access_token無效 = 40001, 不合法的憑證類型 = 40002, 不合法的OpenID = 40003, 不合法的媒體文件類型 = 40004, 不合法的文件類型 = 40005, 不合法的文件大小 = 40006, 不合法的媒體文件id = 40007, 不合法的消息類型 = 40008, 不合法的圖片文件大小 = 40009, 不合法的語音文件大小 = 40010, 不合法的視頻文件大小 = 40011, 不合法的縮略圖文件大小 = 40012, 不合法的APPID = 40013, 不合法的access_token = 40014, 不合法的菜單類型 = 40015, 不合法的按鈕個數1 = 40016, 不合法的按鈕個數2 = 40017, 不合法的按鈕名字長度 = 40018, 不合法的按鈕KEY長度 = 40019, 不合法的按鈕URL長度 = 40020, 不合法的菜單版本號 = 40021, 不合法的子菜單級數 = 40022, 不合法的子菜單按鈕個數 = 40023, 不合法的子菜單按鈕類型 = 40024, 不合法的子菜單按鈕名字長度 = 40025, 不合法的子菜單按鈕KEY長度 = 40026, 不合法的子菜單按鈕URL長度 = 40027, 不合法的自定義菜單使用用戶 = 40028, 不合法的oauth_code = 40029, 不合法的refresh_token = 40030, 不合法的openid列表 = 40031, 不合法的openid列表長度 = 40032, 不合法的請求字符不能包含uxxxx格式的字符 = 40033, 不合法的參數 = 40035, 不合法的請求格式 = 40038, 不合法的URL長度 = 40039, 不合法的分組id = 40050, 分組名字不合法 = 40051, 缺少access_token參數 = 41001, 缺少appid參數 = 41002, 缺少refresh_token參數 = 41003, 缺少secret參數 = 41004, 缺少多媒體文件數據 = 41005, 缺少media_id參數 = 41006, 缺少子菜單數據 = 41007, 缺少oauth_code = 41008, 缺少openid = 41009, access_token超時 = 42001, refresh_token超時 = 42002, oauth_code超時 = 42003, 需要GET請求 = 43001, 需要POST請求 = 43002, 需要HTTPS請求 = 43003, 需要接收者關注 = 43004, 需要好友關系 = 43005, 多媒體文件為空 = 44001, POST的數據包為空 = 44002, 圖文消息內容為空 = 44003, 文本消息內容為空 = 44004, 多媒體文件大小超過限制 = 45001, 消息內容超過限制 = 45002, 標題字段超過限制 = 45003, 描述字段超過限制 = 45004, 鏈接字段超過限制 = 45005, 圖片鏈接字段超過限制 = 45006, 語音播放時間超過限制 = 45007, 圖文消息超過限制 = 45008, 接口調用超過限制 = 45009, 創建菜單個數超過限制 = 45010, 回復時間超過限制 = 45015, 系統分組不允許修改 = 45016, 分組名字過長 = 45017, 分組數量超過上限 = 45018, 不存在媒體數據 = 46001, 不存在的菜單版本 = 46002, 不存在的菜單數據 = 46003, 解析JSON_XML內容錯誤 = 47001, api功能未授權 = 48001, 用戶未授權該api = 50001, 參數錯誤invalid_parameter = 61451, 無效客服賬號invalid_kf_account = 61452, 客服帳號已存在kf_account_exsited = 61453, /// <summary> /// 客服帳號名長度超過限制(僅允許10個英文字符,不包括@及@后的公眾號的微信號)(invalid kf_acount length) /// </summary> 客服帳號名長度超過限制 = 61454, /// <summary> /// 客服帳號名包含非法字符(僅允許英文+數字)(illegal character in kf_account) /// </summary> 客服帳號名包含非法字符 = 61455, /// <summary> /// 客服帳號個數超過限制(10個客服賬號)(kf_account count exceeded) /// </summary> 客服帳號個數超過限制 = 61456, 無效頭像文件類型invalid_file_type = 61457, 系統錯誤system_error = 61450, 日期格式錯誤 = 61500, 日期范圍錯誤 = 61501, //新加入的一些類型,以下文字根據P2P項目格式組織,非官方文字 發送消息失敗_48小時內用戶未互動 = 10706, 發送消息失敗_該用戶已被加入黑名單_無法向此發送消息 = 62751, 發送消息失敗_對方關閉了接收消息 = 10703, 對方不是粉絲 = 10700 } /// <summary> /// 返回接口 /// </summary> interface IWxJsonResult : IJsonResult { ReturnCode errcode { get; set; } } /// <summary> /// 公眾號JSON返回結果(用於菜單接口等) /// </summary> [Serializable] class WxJsonResult : IWxJsonResult { public ReturnCode errcode { get; set; } public string errmsg { get; set; } /// <summary> /// 為P2P返回結果做准備 /// </summary> public virtual object P2PData { get; set; } } #endregion } /// <summary> /// access_token請求后的JSON返回格式 /// </summary> [Serializable] public class AccessTokenResult { /// <summary> /// 獲取到的憑證 /// </summary> public string access_token { get; set; } /// <summary> /// 憑證有效時間,單位:秒 /// </summary> public int expires_in { get; set; } /// <summary> /// 憑證過期有效時間 /// </summary> public DateTime expiresAt { get; set; } } }
通過緩存access_token方式實現以后,同步微信后台用戶數據正常了。
==============
上面第一個方法可以作為C#開發的同學的獲取AccessToken的公共方法,因為還有其他語言開發的同學,所以在這里又增加了一個獲取AccessToken的對外接口
//----------------------------------------------------------------------- // <copyright file="WeChatService.ashx" company="Hairihan"> // Copyright (C) 2016 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace DotNet.UserCenter { using DotNet.Business.HttpUtilities; using DotNet.Utilities; /// <summary> /// WeChatService /// /// 修改記錄 /// /// /// 2016-11-17 版本:1.0 SongBiao 創建 /// /// <author> /// <name>SongBiao</name> /// <date>2016-11-17</date> /// </author> /// </summary> public class WeChatService : IHttpHandler { /// <summary> /// 獲取服務器時間 /// </summary> /// <param name="context"></param> private void GetServerDateTime(HttpContext context) { JsonResult<string> jsonResult = new JsonResult<string>() { Status = true, StatusMessage = "成功獲取服務器時間", Data = DateTime.Now.ToString(BaseSystemInfo.DateTimeFormat) }; context.Response.Write(jsonResult.ToJson()); } /// <summary> /// 獲取用戶中心庫時間 /// </summary> /// <param name="context"></param> private void GetDbDateTime(HttpContext context) { JsonResult<string> jsonResult = new JsonResult<string>(); try { using (IDbHelper dbHelper = DbHelperFactory.GetHelper(BaseSystemInfo.UserCenterDbType, BaseSystemInfo.UserCenterDbConnection)) { string result = DateTime.Parse(dbHelper.GetDbDateTime()).ToString(BaseSystemInfo.DateTimeFormat); jsonResult.Status = true; jsonResult.StatusMessage = "成功獲取用戶中心庫時間"; jsonResult.Data = result; } } catch (Exception ex) { jsonResult.Status = true; jsonResult.StatusMessage = "獲取用戶中心庫時間異常:" + ex.Message; NLogHelper.Trace(ex, "UserService GetDbDateTime 異常"); } context.Response.Write(jsonResult.ToJson()); } /// <summary> /// 獲取AccessToken /// </summary> /// <param name="context"></param> private void GetAccessToken(HttpContext context) { BaseResult baseResult = new BaseResult(); try { string accessToken = WeChatUtilities.GetAccessToken(); if (!string.IsNullOrWhiteSpace(accessToken)) { baseResult.Status = true; baseResult.ResultValue = accessToken; baseResult.StatusMessage = Status.OK.GetDescription(); } else { baseResult.Status = false; baseResult.StatusMessage = "AccessToken獲取失敗。"; } } catch (Exception ex) { baseResult.Status = false; baseResult.StatusMessage = "AccessToken獲取異常:"+ex.Message; } context.Response.Write(baseResult.ToJson()); } public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; if (context.Request["function"] == null) { this.GetServerDateTime(context); } else { string function = context.Request["function"]; if (function.Equals("GetServerDateTime", StringComparison.OrdinalIgnoreCase)) { this.GetServerDateTime(context); } else if (function.Equals("GetAccessToken", StringComparison.OrdinalIgnoreCase)) { this.GetAccessToken(context); } else { context.Response.Write(BaseResult.Error("function對應方法不存在。").ToJson()); } context.Response.End(); } } public bool IsReusable { get { return false; } } } }