关于企业微信对接内部应用开发,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);
        }
    }

  

原文地址:https://www.cnblogs.com/tthjHiroki/p/14451287.html