關(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ō),主要分為五步:
在你的Web服務(wù)器端調(diào)用api,傳入CorpId和CorpSecret,獲取accessToken,即訪問(wèn)令牌。
在服務(wù)器端調(diào)用api,傳入accessToken,獲取JsApiTicket,即JsApi的訪問(wèn)許可(門(mén)票)。
按照既定規(guī)則,在后臺(tái)由JsApiTicket、NonceStr、Timestamp、本頁(yè)面Url生成字符串,計(jì)算SHA1消息摘要,即簽名Signature。
將AgentId、CorpId、Timestamp、NonceStr、Signature等參數(shù)傳遞到前臺(tái),在前臺(tái)調(diào)用api,得到authCode,即授權(quán)碼。
根據(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è)面,即算完成。圖示如下:
其中每個(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反序列化。
核心代碼如下:
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 }
HttpRequestHelper View Code
其中的Model,就不再一一貼出來(lái)了,大家可以根據(jù)官方文檔自己建立,這里只舉一個(gè)例子,即GetAccessToken的返回結(jié)果:
public class AccessTokenModel { public string access_token { get; set; } public int errcode { get; set; } public string errmsg { get; set; } }
我創(chuàng)建了一個(gè)類(lèi)DDApiService,將上述方法做了封裝:
DDApiService View Code
以上是底層核心部分。登錄頁(yè)面的實(shí)現(xiàn)在控制器DDController中,代碼如下:
DDController View Code
視圖View的代碼:
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