最近做微信公众号开发,涉及到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;
}
}
}
}