微信公众号开发之access_token的全局共用

时间:2022-12-23 17:24:53

最近做微信公众号开发,涉及到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的全局共用

通过缓存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;
}
}
}
}