最近作微信公衆號開發,涉及到access_token的緩存問題(避免各自的應用都去取access_token,同時解決微信 appid和appsecret的安全問題),在通用權限管理系統底層增長了實現方法:redis
(access_token默認2小時過時,每取一次,上一次的就自動失效,天天取的次數有限制)json
//----------------------------------------------------------------- // 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方式實現之後,同步微信後臺用戶數據正常了。api
==============緩存
上面第一個方法能夠做爲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; } } } }