钉钉鉴权

前言

  • 公司有项目,需要用到钉钉鉴权,还是dotnet core 版的
  • 百度了一下,发现大多是水文,还有部分是误导的。
  • 于是自己配置了一下
  • 有几点要注意的
  • 后端和前端的 sdk 都是老版本的
  • 新版本的可以按官网,一步一步配置就可以了。
  • ticket 还是要缓存的,频繁请求 token 会
  • 在使用accessToken时,请注意:
    accessToken的有效期为7200秒(2小时),有效期内重复获取会返回相同结果并自动续期,过期后获取会返回新的accessToken。
    开发者需要缓存accessToken,用于后续接口的调用。因为每个应用的accessToken是彼此独立的,所以进行缓存时需要区分应用来进行存储。
    不能频繁调用gettoken接口,否则会受到频率拦截。
  • ticket 说明
    企业内部应用是以应用维度获取jsapi_ticket的,所以在使用的时候需要将jsapi_ticket以appKey为维度进行缓存下来(设置缓存过期时间2小时),并不需要每次都通过接口拉取。

后端代码

// 应用凭证
        // 必填,微应用ID
        private readonly string agentId = "xxxxx";
        private readonly string Appkey = "xxxx";
        private readonly string Appsecret = "xxxxxxxxx";    

        //必填,企业ID
        private readonly string corpId = "xxxxxxx";

        //  获取  token 
        private string GetTestDingToken()
        {
            string token;
            try
            {
                // 获取 access_token
                IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
                OapiGettokenRequest req = new OapiGettokenRequest();
                req.Appkey = Appkey;
                req.Appsecret = Appsecret;
                req.SetHttpMethod("GET");
                OapiGettokenResponse rsp = client.Execute(req);
                token = rsp.AccessToken;

            }
            catch (Exception ex)
            {
                return ex.ToString();
            }

            return token;
        }

        /// <summary>
        ///   获取  jsapi_ticket
        /// </summary>
        /// <returns></returns>
        private string GetTicket()
        {
            string ticket;
            var access_token =  GetTestDingToken();

            try
            {
                IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/get_jsapi_ticket");
                OapiGetJsapiTicketRequest req = new OapiGetJsapiTicketRequest();
                req.SetHttpMethod("GET");
                OapiGetJsapiTicketResponse rsp = client.Execute(req, access_token);
                //ticket = string.IsNullOrEmpty(rsp.Ticket) ? rsp.Ticket : rsp.ErrMsg + "/" + rsp.ErrCode;
                ticket = rsp.Ticket;  // rsp.Body;
            }
            catch (Exception ex)
            {
                ticket = ex.ToString();
            }
            return ticket;
        }


        /// <summary>
        ///    获取 noncestr 随机串
        /// </summary>
        /// <returns></returns>

        private string GetTicketRandomStr(int count)
        {
            string seed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            Random random = new Random();
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < count; i++)
            {
                int number = random.Next(0, seed.Length);
                stringBuilder.Append(seed[number]);
            }
            return stringBuilder.ToString();
        }

        /// <summary>
        ///    获取 timeStamp 时间戳
        /// </summary>
        /// <returns></returns>
        private long GetTimeStamp()
        {
            TimeSpan timteSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(timteSpan.TotalSeconds);
        }


        /// <summary>
        ///    SHA1 加密方法 
        /// </summary>
        /// <returns></returns>
        private static string Sha1(string text)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(text);
            byte[] dataList = SHA1.Create().ComputeHash(bytes);
            StringBuilder stringBuilder = new StringBuilder();
            foreach (byte data in dataList)
            {
                stringBuilder.Append(data.ToString("X2"));
            }
            return stringBuilder.ToString();
        }


        /// <summary>
        ///  最后在服务器端生成前端页面需要的参数信息
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [Route("PostConfig")]
        [NoSign]
        public async Task<IActionResult> PostConfig([FromBody] ReqConfig param)
        {
            ObjectResultEx obj = new ObjectResultEx(-1, null, null);

            //var usname = _user.UserName;
            var user = await _userSer.Get(_user.UserID);
            if (user != null)
            {
                long timeStamp = GetTimeStamp();

                string ticket = GetTicket(); // "OJXrpQ82w5ufOFSfQrJme9Al0rktlMuGcucDYkuE1gjCbI0SNvnYGMfjxW2PHa6knOcUrmRpwtlJLI9qJAOK8E"; // GetTicket();

                string noncestr = GetTicketRandomStr(20);

                string url = HttpUtility.UrlDecode(param.GetUrl);
                string plainTex = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "×tamp=" + timeStamp.ToString() + "&url=" + url;
                string signature = Sha1(plainTex).ToLower();

                // 必填,微应用ID
                string agentId = agentId;
                //必填,企业ID
                string corpId = corpId;

                obj.Value = new { timeStamp, noncestr, signature, ticket, agentId, corpId };
                obj.StatusCode = 200;

            }

            return Ok(obj);
        }

        public class ReqConfig 
        {
            public string GetUrl { get; set; }
            public string Ticket { get; set; }        
        }

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <script src="./vconsole.min.js"></script>
      <script type="text/javascript">
        var vConsole = new VConsole();
      </script>


    <script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.25/dingtalk.open.js"></script>


</head>
<body>
   <div> 测试钉钉鉴权</div>
      <div id="msg" style="width: 100%; overflow: auto;"></div>
      <div id="url" style="width: 100%; overflow: auto;"></div>
      <div id="start" style="width: 100%; height: 100px; border: 1px #ddd solid;">录音</div>
      <div id="end" style="width: 100%; height: 100px; border: 1px #ddd solid; margin-top: 40px;">停止录音</div>

      <div id="content"></div>

    <script type="text/javascript">

            var xmlhttp;
            if (window.XMLHttpRequest)
            {
                //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
                xmlhttp=new XMLHttpRequest();
            }
            else
            {
                // IE6, IE5 浏览器执行代码
                xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
            }
            xmlhttp.onreadystatechange=function()
            {
                if (xmlhttp.readyState==4 && xmlhttp.status==200)
                {
  
                    var res = JSON.parse(xmlhttp.responseText).value;
                    document.getElementById("msg").innerHTML = res.timeStamp;

                    dd.config({

                        agentId: res.agentId, // 必填,微应用ID


                        corpId: res.corpId,//必填,企业ID

                        timeStamp: res.timeStamp, // 必填,生成签名的时间戳

                        nonceStr: res.noncestr, // 必填,自定义固定字符串。

                        signature: res.signature, // 必填,签名

                        // type:0/1,   //选填。0表示微应用的jsapi,1表示服务窗的jsapi;不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持

                        jsApiList : [
                            // 'biz.contact.choose',
                            // 开始录音
                            'device.audio.startRecord',
                            // 停止录音
                            'device.audio.stopRecord',
                            // 60 秒录音停止监听
                            'device.audio.onRecordEnd',
                            // 播放录音
                            'device.audio.play',
                            // 语音转文字
                            'device.audio.translateVoice',


                        ] // 必填,需要使用的jsapi列表,注意:不要带dd。
                    });

                    dd.error(function (err) {
                        alert('dd error: ' + JSON.stringify(err));
                    })//该方法必须带上,用来捕获鉴权出现的异常信息,否则不方便排查出现的问题

                    dd.ready(function() {
                          console.log('dd ready')
                          document.getElementById("start").addEventListener("click", function () {
                                console.log("start")
                                dd.device.audio.startRecord({
                                    onSuccess : function () {//支持最长为300秒(包括)的音频录制,默认60秒(包括)。
                                        console.log("success")
                                    },
                                    onFail : function (err) {
                                         console.log("err")
                                    }
                                });
                          })

                          document.getElementById("end").addEventListener("click", function () {
                                console.log("end")
                                dd.device.audio.stopRecord({
                                    onSuccess : function(res){
                                        // res.mediaId; // 返回音频的MediaID,可用于本地播放和音频下载
                                        // res.duration; // 返回音频的时长,单位:秒
                                        console.log(res.mediaId);
                                        console.log(res.duration);

                                        dd.device.audio.play({
                                            localAudioId : res.mediaId,
                                         
                                            onSuccess : function () {
                                                 console.log("播放")
                                            },
                                         
                                            onFail : function (err) {
                                            }
                                        });


                                        dd.device.audio.translateVoice({
                                            mediaId : res.mediaId,
                                            duration : 5.0,
                                            onSuccess : function (res) {
                                                // res.mediaId; // 转换的语音的mediaId
                                                // res.content; // 语音转换的文字内容
                                                document.getElementById("content").innerHTML =  res.content;
                                            }
                                        });
                                    },
                                    onFail : function (err) {

                                        console.log(err)
                                    }
                                });
                          })
                     });
                }else{
                     document.getElementById("msg").innerHTML="请求失败";
                }
            }
            xmlhttp.open("POST","后端地址",true);

            // 添加请求头  
            xmlhttp.setRequestHeader('Content-Type', 'application/json');  
            // 按需要添加网站认证
            xmlhttp.setRequestHeader('Authorization', 'Bearer xxx');  

            var data = JSON.stringify({
                // ios 需要解码 decodeURIComponent(location.href)
                getUrl: location.href, 
            });
            xmlhttp.send(data);
    </script>
    
</body>
</html>