zoukankan      html  css  js  c++  java
  • 关于企业微信对接内部应用开发,access_token的管理机制和业务接口调用项目实战的八个要点

    经过不断的探索,终于在代码上完善了企业微信对接内部开发。由于企业微信token的管理机制比较特别,因此这里拎出来单独解释如何管理企业微信token(留意下方的粗体字)。

    1.企业微信对接内部应用开发,基本上只能在获取到access_token后才能调用其他业务接口,因此access_token的管理非常重要,也是第一步要解决的问题。
    2.access_token的管理机制不同于微信、公众号的token。根本原因在于企业微信access_token针对应用,而其他的token针对用户。显著的影响就是token的保存方式和代码与众不同。(钉钉对接内部应用开发用到的access_token同理)
    3.access_token的过期时间由企业微信官方控制。可能某段时间内从企业微信官方获取的access_token都是同一个,无法由开发者强制更新
    4.根据官方文档的说明,access_token应在首次获取后由开发者保存。过期时间接近2小时,有可能顺延到接近4小时。经过测试发现,在请求gettoken接口获取tokenA后的2小时内,从某个时刻开始重复请求gettoken接口,获取的是tokenB。但此刻用tokenA调用业务接口仍然有效,并且tokenA的可用时间得到了很大延长,由此证明tokenA是有平滑过期时间,以保证某些使用tokenA的线程执行业务接口仍然有效。但这不妨碍更新本地缓存。缓存里只需要保存最新获取的tokenB就可以了
    5.可以使用缓存保存企业微信的access_token,不用担心项目是否需要在多个服务器部署。因为所有用户共用同一个access_token,企业微信会管理和平滑更新access_token。
    6.代码上对于更新access_token的操作应进行加锁,防止在多线程情况下出现多次调用企业微信gettoken接口和写缓存的操作。
    7.在外部应该通过委托调用业务方法而不是直接调用业务方法。在以下代码的示例中,调用顺序大概是“外部->DelegateHelper->ApiHelper->ATokenHelper”。因为调用业务接口失败可能由access_token过期引起,这时候不可能直接返回告诉用户操作失败,应该更新凭证然后重新执行业务逻辑,这需要委托的帮助。在外部直接调用ApiHelper会使得外部表现臃肿并且难以维护。
    8.敲黑板划重点:开发者不要过多理会access_token什么时候过期,而是注重调用业务接口失败后应该怎么做!!!!!!!!!!! 代码中设置的缓存过期时间主要是为了按时释放缓存。

    注:demo中涉及到json解析、http模拟请求的代码,就不提供这部分源码了。demo里有大量注释,可根据注释自己实现对应的功能。
    1.ATokenHelper.cs

    /// <summary>
        /// 企业微信access_token的管理中心
        /// 官方文档:https://work.weixin.qq.com/api/doc/90000/90135/91039
        /// 全局错误码参考:https://work.weixin.qq.com/api/doc/90000/90139/90313
        /// </summary>
        public sealed class ATokenHelper
        {
            private static readonly object _Object = new object();
     
            private static readonly string _CacheName = "access_token";
     
            /// <summary>
            /// 尝试从缓存获取access_token
            /// </summary>
            /// <returns>返回企业微信的token</returns>
            public static string GetAToken()
            {
                string accessToken;
                try
                {
                    //1.【尝试从缓存中读取token】
                    accessToken = Convert.ToString(CacheHelper.GetCache(_CacheName));
                    //2.若缓存里没有token,则请求企业微信接口获取token
                    if (string.IsNullOrEmpty(accessToken))
                    {
                        accessToken = RequestForToken();
                    }
                    return accessToken;
                }
                catch
                {
                    return string.Empty;
                }
            }
     
            /// <summary>
            /// 重新获取token
            /// </summary>
            public static string RequestForToken()
            {
                string accessToken;
                try
                {
                    //1.尝试从缓存里获取当前token
                    string strTokenOld = Convert.ToString(CacheHelper.GetCache(_CacheName));
                    lock (_Object)
                    {
                        //2.再次从缓存里获取当前token
                        string strTokenNew = Convert.ToString(CacheHelper.GetCache(_CacheName));
                        //3.判断锁前和锁后从缓存里读取的值:若一致,说明目前没人操作
                        if (strTokenOld == strTokenNew)
                        {
                            //【拼接企业微信消息推送接口的地址】
                            string getATokenUrl = string.Format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}",
                                ConfigurationManager.AppSettings["QYWX.CorpID"], ConfigurationManager.AppSettings["QYWX.CorpSecret"]);
                            //【发起Get请求】
                            string strResp = getATokenUrl.Get();
                            //【从返回的json里获取状态码节点】
                            string strErrorCode = strResp.JsonCutApart("errcode");
                            //【企业微信的状态码不为0则表示请求失败】
                            if (strErrorCode != "0")
                            {
                                return string.Empty;
                            }
                            //【从返回的json里获取过期时间节点】
                            int iExpireTime = Convert.ToInt32(strResp.JsonCutApart("expires_in"));
                            //【从返回的json里获取企业微信token
                            accessToken = strResp.JsonCutApart(_CacheName);
                            //【保存到缓存中,并且设置过期时间】
                            CacheHelper.SetCache(_CacheName, accessToken, iExpireTime);
                        }
                        else
                        {
                            //4.锁前和锁后的值不一致,说明已经写入了新值
                            accessToken = strTokenNew;
                        }
                    }
                    return accessToken;
                }
                catch
                {
                    return string.Empty;
                }
            }
     
            /// <summary>
            /// 判断接口调用失败是否由access_token过期引起
            /// </summary>
            /// <param name="_ErrorCode">全局错误码</param>
            /// <returns>返回结果</returns>
            public static bool CheckATokenExpire(string _ErrorCode)
            {
                //全局错误码42001表示token过期
                if (_ErrorCode == "42001")
                {
                    //更新token
                    RequestForToken();
                    return true;
                }
                return false;
            }
        }
    

      

    2.ApiHelper.cs

    /// <summary>
        /// 针对代用企业微信业务接口的方法封装
        /// </summary>
        public class ApiHelper
        {
            /// <summary>
            /// 发送文本消息
            /// </summary>
            /// <param name="_UserId">接收者</param>
            /// <param name="_Msg">消息</param>
            /// <param name="_RespCode">返回状态码</param>
            public static void SendMsg(string _ToUser, string _Msg, out string _RespCode)
            {
                _RespCode = string.Empty;
                try
                {
                    var jsonData = new
                    {
                        touser = _ToUser,
                        toparty = "",
                        totag = "",
                        msgtype = "text",
                        agentid = ConfigurationManager.AppSettings["QYWX.AgentID"],
                        text = new { content = _Msg },
                        safe = 1,
                        enable_id_trans = 0,
                        enable_duplicate_check = 0,
                        duplicate_check_interval = 1800
                    };
     
                    string token = ATokenHelper.GetAToken();
                    //【拼接请求接口的地址】
                    string strSendUrl = string.Format("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}", token);
                    //【发起Post请求】
                    string strResp = strSendUrl.Post(jsonData.ToJson(), ContentType.ApplicationJson);
                    //【从返回的json里获取状态码节点】
                    _RespCode = strResp.JsonCutApart("errcode");
                }
                catch
                {
     
                }
            }
        }
    

    3.DelegateHelper.cs

    public delegate void Action(string _Param1, string _Param2, out string _RespCode);
     
        public class DelegateHelper
        {
            /// <summary>
            /// 调用示例:DelegateHelper.Do(ApiHelper.SendMsg, "Liuyu", "您有一条新的消息")
            /// </summary>
            /// <returns>返回调用结果 true:成功 false:失败</returns>
            public static bool Do(Action _Action, string _Param1, string _Param2)
            {
                string respCode = string.Empty;
                if (_Action != null)
                {
                    _Action(_Param1, _Param2, out respCode);
                    //检查access_token是否过期,若过期则更新,并且重新执行业务方法
                    if (ATokenHelper.CheckATokenExpire(respCode))
                    {
                        _Action(_Param1, _Param2, out respCode);
                    }
                }
                return respCode == "0";
            }
        }
    

      

    4.调用示例Program.cs

    class Program
        {
            static void Main(string[] args)
            {
                bool result = DelegateHelper.Do(ApiHelper.SendMsg, "Liu.Yu", "您收到了一条测试信息");
                Console.WriteLine("处理结果:{0}", result);
            }
        }
    

      

  • 相关阅读:
    关于jQuery方法解析(一)append-参数设置问题
    CSS动画
    关于html CSS 绝对相对布局问题
    Chrome自带的开发者工具使用方法教程
    web常见漏洞及防范方法
    前端性能优化 Web前端应该从哪些方面来优化网站?
    属性的特征描述可以分为两类:数据属性和访问器属性
    iScroll.js的用法
    百度前端学院在线学习参考资料
    GET和POST的区别,何时使用POST?
  • 原文地址:https://www.cnblogs.com/tthjHiroki/p/14451287.html
Copyright © 2011-2022 走看看