關(guān)于釘釘

釘釘是阿里推出的企業(yè)移動(dòng)OA平臺(tái),本身提供了豐富的通用應(yīng)用,同時(shí)其強(qiáng)大的后臺(tái)API接入能力讓企業(yè)接入自主開(kāi)發(fā)的應(yīng)用成為可能,可以讓開(kāi)發(fā)者實(shí)現(xiàn)幾乎任何需要的功能。

近期因?yàn)楣ぷ餍枰芯苛艘幌箩斸數(shù)慕尤?,發(fā)現(xiàn)其接入文檔、SDK都是基于java編寫(xiě)的,而我們的企業(yè)網(wǎng)站使用Asp.Net MVC(C#)開(kāi)發(fā),所以接入只能從頭自己做SDK。

接入主要包括免登、獲取數(shù)據(jù)、修改數(shù)據(jù)等接口。

免登流程

首先需要理解一下釘釘?shù)拿獾橇鞒?,借用官方文檔的圖片:

是不是很熟悉?是的,基本是按照OAUTH的原理來(lái)的,版本嘛,里面有計(jì)算簽名的部分,我覺(jué)得應(yīng)該是OAUTH1.0。

有的讀者會(huì)問(wèn),那第一步是不是應(yīng)該跳轉(zhuǎn)到第三方認(rèn)證頁(yè)面啊。我覺(jué)得“魔法”就藏在用來(lái)打開(kāi)頁(yè)面的釘釘內(nèi)置瀏覽器里,在dd.config()這一步里,“魔法”就生效了。

 

其實(shí)簡(jiǎn)單來(lái)說(shuō),主要分為五步:

  1. 在你的Web服務(wù)器端調(diào)用api,傳入CorpId和CorpSecret,獲取accessToken,即訪問(wèn)令牌。

  2. 在服務(wù)器端調(diào)用api,傳入accessToken,獲取JsApiTicket,即JsApi的訪問(wèn)許可(門(mén)票)。

  3. 按照既定規(guī)則,在后臺(tái)由JsApiTicket、NonceStr、Timestamp、本頁(yè)面Url生成字符串,計(jì)算SHA1消息摘要,即簽名Signature。

  4. 將AgentId、CorpId、Timestamp、NonceStr、Signature等參數(shù)傳遞到前臺(tái),在前臺(tái)調(diào)用api,得到authCode,即授權(quán)碼。

  5. 根據(jù)授權(quán)碼,在前臺(tái)或后臺(tái)調(diào)用api,獲得userId,進(jìn)而再根據(jù)userId,調(diào)用api獲取用戶詳細(xì)信息。

PS:為什么需要在后臺(tái)完成一些api的調(diào)用呢?應(yīng)該是因?yàn)閖s跨域調(diào)用的問(wèn)題,我具體沒(méi)有深究。

實(shí)踐方法

理解了上述步驟,我對(duì)登陸過(guò)程的實(shí)現(xiàn)也大致有了一個(gè)設(shè)想,既然免登需要前后端一起來(lái)完成,那就添加一個(gè)專(zhuān)門(mén)的登陸頁(yè)面,將登陸過(guò)程都在里面實(shí)現(xiàn),將登陸結(jié)果寫(xiě)入到Session,并重定向回業(yè)務(wù)頁(yè)面,即算完成。圖示如下:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

其中每個(gè)api的調(diào)用方式,在官方文檔中都有說(shuō)明。同時(shí),我在阿里云開(kāi)發(fā)者論壇找到了網(wǎng)友提供的SDK,有興趣可以下載:釘釘非官方.Net SDK

另外,GitHub上還有官方的JQuery版免登開(kāi)發(fā)Demo,可以參考:GitHub JQuery免登。

我參考的是.Net SDK,將其中的代碼,提取出了我所需要的部分,做了簡(jiǎn)化處理。基本原理就是每次調(diào)用API都是發(fā)起HttpRequest,將結(jié)果做JSON反序列化。

核心代碼如下:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

  1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Web;  5 using System.IO;  6 using Newtonsoft.Json;  7 using Newtonsoft.Json.Linq;  8 using DDApi.Model;  9  10 namespace DDApi 11 { 12     public static class DDHelper 13     { 14         public static string GetAccessToken(string corpId, string corpSecret) 15         { 16             string url = string.Format("https://oapi.dingtalk.com/gettoken?corpid={0}&corpsecret={1}", corpId, corpSecret); 17             try 18             { 19                 string response = HttpRequestHelper.Get(url); 20                 AccessTokenModel oat = Newtonsoft.Json.JsonConvert.DeserializeObject<AccessTokenModel>(response); 21  22                 if (oat != null) 23                 { 24                     if (oat.errcode == 0) 25                     { 26                         return oat.access_token; 27                     } 28                 } 29             } 30             catch (Exception ex) 31             { 32                 throw; 33             } 34             return string.Empty; 35         } 36  37         /* https://oapi.dingtalk.com/get_jsapi_ticket?access_token=79721ed2fc46317197e27d9bedec0425 38          * 
 39          * errmsg    "ok" 40          * ticket    "KJWkoWOZ0BMYaQzWFDF5AUclJOHgO6WvzmNNJTswpAMPh3S2Z98PaaJkRzkjsmT5HaYFfNkMdg8lFkvxSy9X01" 41          * expires_in    7200 42          * errcode    0 43          */ 44         public static string GetJsApiTicket(string accessToken) 45         { 46             string url = string.Format("https://oapi.dingtalk.com/get_jsapi_ticket?access_token={0}", accessToken); 47             try 48             { 49                 string response = HttpRequestHelper.Get(url); 50                 JsApiTicketModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<JsApiTicketModel>(response); 51  52                 if (model != null) 53                 { 54                     if (model.errcode == 0) 55                     { 56                         return model.ticket; 57                     } 58                 } 59             } 60             catch (Exception ex) 61             { 62                 throw; 63             } 64             return string.Empty; 65         } 66  67         public static long GetTimeStamp() 68         { 69             TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 70             return Convert.ToInt64(ts.TotalSeconds); 71         } 72  73         public static string GetUserId(string accessToken, string code) 74         { 75             string url = string.Format("https://oapi.dingtalk.com/user/getuserinfo?access_token={0}&code={1}", accessToken, code); 76             try 77             { 78                 string response = HttpRequestHelper.Get(url); 79                 GetUserInfoModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<GetUserInfoModel>(response); 80  81                 if (model != null) 82                 { 83                     if (model.errcode == 0) 84                     { 85                         return model.userid; 86                     } 87                     else 88                     { 89                         throw new Exception(model.errmsg); 90                     } 91                 } 92             } 93             catch (Exception ex) 94             { 95                 throw; 96             } 97             return string.Empty; 98         } 99 100         public static string GetUserDetailJson(string accessToken, string userId)101         {102             string url = string.Format("https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}", accessToken, userId);103             try104             {105                 string response = HttpRequestHelper.Get(url);106                 return response;107             }108             catch (Exception ex)109             {110                 throw;111             }112             return null;113         }114 115         public static UserDetailInfo GetUserDetail(string accessToken, string userId)116         {117             string url = string.Format("https://oapi.dingtalk.com/user/get?access_token={0}&userid={1}", accessToken, userId);118             try119             {120                 string response = HttpRequestHelper.Get(url);121                 UserDetailInfo model = Newtonsoft.Json.JsonConvert.DeserializeObject<UserDetailInfo>(response);122 123                 if (model != null)124                 {125                     if (model.errcode == 0)126                     {127                         return model;128                     }129                 }130             }131             catch (Exception ex)132             {133                 throw;134             }135             return null;136         }137 138         public static List<DepartmentInfo> GetDepartmentList(string accessToken, int parentId = 1)139         {140             string url = string.Format("https://oapi.dingtalk.com/department/list?access_token={0}", accessToken);141             if (parentId >= 0)142             {143                 url += string.Format("&id={0}", parentId);144             }145             try146             {147                 string response = HttpRequestHelper.Get(url);148                 GetDepartmentListModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<GetDepartmentListModel>(response);149 150                 if (model != null)151                 {152                     if (model.errcode == 0)153                     {154                         return model.department.ToList();155                     }156                 }157             }158             catch (Exception ex)159             {160                 throw;161             }162             return null;163         }164     }165 }

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn) HttpRequestHelper View Code

其中的Model,就不再一一貼出來(lái)了,大家可以根據(jù)官方文檔自己建立,這里只舉一個(gè)例子,即GetAccessToken的返回結(jié)果:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

    public class AccessTokenModel
    {        public string access_token { get; set; }        public int errcode { get; set; }        public string errmsg { get; set; }
    }

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn)

 我創(chuàng)建了一個(gè)類(lèi)DDApiService,將上述方法做了封裝:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn) DDApiService View Code

以上是底層核心部分。登錄頁(yè)面的實(shí)現(xiàn)在控制器DDController中,代碼如下:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn) DDController View Code

視圖View的代碼:

移動(dòng)開(kāi)發(fā)培訓(xùn),Android培訓(xùn),安卓培訓(xùn),手機(jī)開(kāi)發(fā)培訓(xùn),手機(jī)維修培訓(xùn),手機(jī)軟件培訓(xùn) Login.cshtml View Code

其中nonstr理論上最好應(yīng)該每次都隨機(jī),留待讀者去完成吧:-)

釘釘免登就是這樣,只要弄懂了就會(huì)覺(jué)得其實(shí)不難,還順便理解了OAUTH。

后續(xù)改進(jìn)

這個(gè)流程沒(méi)有考慮到AccessToken、JsApiTicket的有效期時(shí)間(2小時(shí)),因?yàn)檎麄€(gè)過(guò)程就在一個(gè)頁(yè)面中都完成了。如果想要進(jìn)一步擴(kuò)展,多次調(diào)用api的話,需要考慮到上述有效期。

如果為了圖簡(jiǎn)便每都去獲取AccessToken也是可以的,但是會(huì)增加服務(wù)器負(fù)擔(dān),而且api的調(diào)用頻率是有限制的(1500次/s好像),所以應(yīng)當(dāng)采取措施控制。例如可以將AccessToken、JsApiTicket存放在this.HttpContext.Application["accessToken"]中,每次判斷有效期是否過(guò)期,如果過(guò)期就調(diào)用api重新申請(qǐng)一個(gè)。

以上就是這樣,感謝閱讀。

http://www.cnblogs.com/pleiades/p/7140318.html