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

最近做微信公众号开发,涉及到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;
            }
        }
    }
}
原文地址:https://www.cnblogs.com/hnsongbiao/p/6069963.html