在《釘釘開發系列(八)二維碼掃描登陸的實現》介紹了一種掃碼登陸的方式,該方式是本身產生二維碼,二維碼中的URL指到自身的服務器頁面,在該頁面中以JSSDK的方式來獲取釘釘用戶的信息。釘釘官方提供了另外兩種掃碼登陸的方式,能夠參見釘釘官網。javascript
先申請獲取相應的appid和appsecret,而後架設一個服務端,好比有頁面ddqrlogin.aspx,而後將該頁面的URL使用URL編碼,對應到https://oapi.dingtalk.com/connect/qrconnect?appid=APPID&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI中的REDIRECT_URI,即用該URL編碼後的值替代REDIRECT_URI。而後將該URL嵌入到web頁面中。若是是winform的,能夠直接用webbrowser,將其URL設置爲前面拼成的一長串URL。同時將ScriptErrorsSuppressed設置爲false,以屏蔽JS錯誤時的彈窗,設置ScrollBarsEnabled爲false,以便於調整窗體的大小。css
同時設置DocumentCompleted事件,以便在掃描成功後,讀取返回的數據,代碼以下。html
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { if (webBrowser1.Document.Url.AbsolutePath.Contains("ddqrlogin")) { var dduseridPackageJson = $"{webBrowser1.Document.InvokeScript("GetDDUserId")}"; MessageBox.Show(dduseridPackageJson ); } }其中webBrowser1.Document.InvokeScript("GetDDUserId")調用的是ddqrlogin.aspx的JS函數GetDDUserId.
在服務端ddqrlogin.aspx代碼以下java
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ddqrlogin.aspx.cs" Inherits="DingDingQRLogin.ddqrlogin" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <script type="text/javascript"> function GetDDUserId() { try { var hiddenField = document.getElementById("<%=HiddenFieldDDUserId.ClientID%>"); var ddUserId = hiddenField.value; return ddUserId; } catch (e) { alert(e.message); } } </script> </head> <body> <form id="form1" runat="server"> <asp:HiddenField ID="HiddenFieldDDUserId" runat="server" /> <div style="width: 611px; height: 600px; background-color: #2F4F4F;position:absolute;"> <div style="margin-left: 123px; margin-top: 74px; width: 365px; height: 292px; background-color: #F9F9F9; text-align: center; position: absolute;"> <div id="loginResultInfo" style="position: absolute; top: 50%; left: 50%;" runat="server"> </div> </div> </div> </form> </body> </html>服務端後臺代碼以下
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { var tempAuthCode = Request.QueryString["code"]; var state = Request.QueryString["state"]; var userIdPackage = SdkTool_QRLogin.FetchDDUserIdTempAuthCode(tempAuthCode); HiddenFieldDDUserId.Value = userIdPackage.ToJSON(); loginResultInfo.InnerText = (userIdPackage.IsOK()) ? "登陸成功" : userIdPackage.ErrMsg; } }其中FetchDDUserIdTempAuthCode是獲取釘釘的用戶id,具體代碼以下。
public static class SdkTool_QRLogin { #region 全局變量 /// <summary> /// 基於appid獲取的票據 /// </summary> public static DDAppAccessToken AppAccessToken = DDAppAccessToken.GetInstance(); #endregion #region UpdateQRAccessToken /// <summary> /// 更新應用票據 /// </summary> /// <returns></returns> public static void UpdateAppAccessToken(bool forceUpdate = false) { if (!forceUpdate && !AppAccessToken.IsExpired()) {//沒有強制更新,而且沒有超過緩存時間 return; } string appId = ConfigTool.FetchAppId(); string appSecret = ConfigTool.FetchAppSecret(); string TokenUrl = QRUrls.SNS_GET_TOKEN; string apiurl = $"{TokenUrl}?{QRKeys.appid}={appId}&{QRKeys.appsecret}={appSecret}"; DDTokenPackage tokenResult = DDRequestAnalyzer.Get<DDTokenPackage>(apiurl); if (tokenResult.IsOK()) { AppAccessToken.Value = tokenResult.Access_token; AppAccessToken.Begin = DateTime.Now; } } #endregion #region FetchPersistentCode Function /// <summary> /// 獲取持久受權碼 /// </summary> /// <param name="tempAuthCode"></param> /// <returns></returns> public static DDPersistentCode FetchPersistentCode(string tempAuthCode) { string apiUrl = FormatApiUrlWithAppToken(QRUrls.SNS_GET_PERSISTENT_CODE); var data = new { tmp_auth_code = tempAuthCode }; DDPersistentCode result = DDRequestAnalyzer.Post<DDPersistentCode>(apiUrl, data.ToJSON()); return result; } #endregion #region FetchSnsToken Function /// <summary> /// 獲取SNS票據 /// </summary> /// <param name="openId"></param> /// <param name="persistentCode"></param> /// <param name="forceUpdate"></param> public static DDSnsToken FetchSnsToken(string openId, string persistentCode, bool forceUpdate) { string apiUrl = FormatApiUrlWithAppToken(QRUrls.SNS_GET_SNS_TOKEN); var data = new { openid = openId, persistent_code = persistentCode }; DDSnsToken SnsToken = new DDSnsToken(); var result = DDRequestAnalyzer.Post<DDSnsToken>(apiUrl, data.ToJSON()); if (result.IsOK()) { SnsToken.ExpiresIn = result.ExpiresIn; SnsToken.Value = result.Value; SnsToken.Begin = DateTime.Now; } return SnsToken; } #endregion #region FetchUserInfo Function /// <summary> /// 基於臨時獲權碼獲取用戶信息 /// </summary> /// <param name="tempAuthCode">臨時受權碼</param> /// <returns></returns> public static DDSnsUserInfo FetchUserInfo(string tempAuthCode) { var persistentCodePackage = FetchPersistentCode(tempAuthCode); DDSnsUserInfo snsUserInfoPackage = new DDSnsUserInfo(); if (!persistentCodePackage.IsOK()) { snsUserInfoPackage.ErrCode = DDErrCodeEnum.Unknown; snsUserInfoPackage.ErrMsg = $"使用tempAuthCode({tempAuthCode})獲取"; return snsUserInfoPackage; } var snsToken = FetchSnsToken(persistentCodePackage.OpenId, persistentCodePackage.PersistentCode, false); string apiUrl = $"{QRUrls.SNS_GET_USER_INFO}?{QRKeys.sns_token}={snsToken.Value}"; snsUserInfoPackage = DDRequestAnalyzer.Get<DDSnsUserInfo>(apiUrl); return snsUserInfoPackage; } #endregion #region FetchUserInfo Function /// <summary> /// 基於臨時獲取DDUserId /// </summary> /// <param name="tempAuthCode">臨時受權碼</param> /// <returns></returns> public static DDUserIdPackage FetchDDUserIdTempAuthCode(string tempAuthCode) { var snsUserInfoPackage = FetchUserInfo(tempAuthCode); DDUserIdPackage userIdPackage = new DDUserIdPackage(); if (!snsUserInfoPackage.IsOK()) { userIdPackage.ErrCode = snsUserInfoPackage.ErrCode; userIdPackage.ErrMsg = snsUserInfoPackage.ErrMsg; return userIdPackage; } userIdPackage = FetchDDUserIdByUnionId(snsUserInfoPackage.user_info.unionid); return userIdPackage; } #endregion #region FetchDDUserIdByUnionId Function /// <summary> /// 基於UnionId獲取DDUserId /// </summary> /// <param name="unionid">用戶在當前釘釘開放平臺帳號範圍內的惟一標識,同一個釘釘開放平臺帳號能夠包含多個開放應用,同時也包含ISV的套件應用及企業應用</param> /// <returns></returns> public static DDUserIdPackage FetchDDUserIdByUnionId(string unionid) { DDUserIdPackage userIdPackage = new DDUserIdPackage(); var accessTokenPackage = AuthService.GetAccessToken(); if (!accessTokenPackage.IsOK()) { userIdPackage.ErrCode = DDErrCodeEnum.Unknown; userIdPackage.ErrMsg = accessTokenPackage.Message; return userIdPackage; } DDAccessToken accessTokenOfCorpId = accessTokenPackage.Data; if (accessTokenOfCorpId == null) { userIdPackage.ErrCode = DDErrCodeEnum.Unknown; userIdPackage.ErrMsg = "accessTokenOfCorpId is null"; return userIdPackage; } string apiUrl = $"{QRUrls.USER_GET_USERID_BY_UNIONID}?{QRKeys.access_token}={accessTokenOfCorpId.Value}"; apiUrl += $"&{QRKeys.access_token}={AppAccessToken.Value}&{QRKeys.unionid}={unionid}"; userIdPackage = DDRequestAnalyzer.Get<DDUserIdPackage>(apiUrl); return userIdPackage; } #endregion #region FormatApiUrlWithAppToken Function public static String FormatApiUrlWithAppToken(String url, bool forceUpdate = false) { UpdateAppAccessToken(forceUpdate); string apiurl = $"{url}?{QRKeys.access_token}={AppAccessToken.Value}"; return apiurl; } #endregion }
相關的其餘類以下web
DDAppAccessTokenapi
public class DDAppAccessToken : DDAccessToken { #region 內部變量 private static readonly object _lockObj = new object(); private static DDAppAccessToken _instance = null; #endregion private DDAppAccessToken() { } #region GetInstance /// <summary> /// 獲取實例(單例) /// </summary> /// <returns></returns> public static DDAppAccessToken GetInstance() { if (_instance != null) { return _instance; } lock (_lockObj) { if (_instance == null) { _instance = new DDAppAccessToken(); } } return _instance; } #endregion #region IsExpired /// <summary> /// 是否過時 /// </summary> /// <returns></returns> public bool IsExpired() { if (Begin.AddSeconds(ConstVars.APP_ACCESS_TOKEN_CACHE_TIME) >= DateTime.Now) { return false; } return true; } #endregion }其中DDAccessToken能夠參看前面系列的代碼。
DDPersistenCode.cs緩存
/// <summary> /// 持久受權碼 /// </summary> public class DDPersistentCode : DDBaseResult { /// <summary> /// 用戶在當前開放應用內的惟一標識 /// </summary> [JsonProperty("openid")] public String OpenId { get; set; } /// <summary> /// 用戶給開放應用受權的持久受權碼,此碼目前無過時時間 /// </summary> [JsonProperty("persistent_code")] public string PersistentCode { get; set; } /// <summary> /// 用戶在當前釘釘開放平臺帳號範圍內的惟一標識,同一個釘釘開放平臺帳號能夠包含多個開放應用,同時也包含ISV的套件應用及企業應用 /// </summary> [JsonProperty("unionid")] public string UnionId { get; set; } }其中JsonProperty是JSON庫Newtonsoft的。
DDSnsToken.cs服務器
public class DDSnsToken : DDBaseResult { /// <summary> /// sns_token的過時時間 /// </summary> [JsonProperty("expires_in")] public int ExpiresIn { get; set; } /// <summary> ///用戶受權的token /// </summary> [JsonProperty("sns_token")] public string Value { get; set; } /// <summary> /// 票據的開始時間 /// </summary> public DateTime Begin { get; set; } #region IsExpired /// <summary> /// 是否過時 /// </summary> /// <returns></returns> public bool IsExpired() { if (Begin.AddSeconds(ExpiresIn) >= DateTime.Now) { return false; } return true; } #endregion }DDSnsUserInfo.cs
public class DDSnsUserInfo : DDBaseResult { /// <summary> /// 企業信息(默認不返回) /// </summary> public SnsCorpInfo[] corp_info { get; set; } /// <summary> /// 用戶信息 /// </summary> public SnsUserInfo user_info { get; set; } } #region SnsCorpInfo /// <summary> /// 企業信息(默認不返回) /// </summary> public class SnsCorpInfo { /// <summary> /// 企業名稱(默認不返回) /// </summary> public string corp_name { get; set; } /// <summary> /// 企業是否通過釘釘認證(默認不返回) /// </summary> public bool is_auth { get; set; } /// <summary> /// 當前用戶是否爲該企業的管理人員(默認不返回) /// </summary> public bool is_manager { get; set; } /// <summary> /// 該企業的權益等級(默認不返回) /// </summary> public int rights_level { get; set; } } #endregion #region SnsUserInfo /// <summary> /// 用戶信息 /// </summary> public class SnsUserInfo { /// <summary> /// 通過處理的手機號(默認不返回) /// </summary> public string maskedMobile { get; set; } /// <summary> /// 用戶在釘釘上面的暱稱 /// </summary> public string nick { get; set; } /// <summary> /// 用戶在當前開放應用內的惟一標識 /// </summary> public string openid { get; set; } /// <summary> /// 用戶在當前開放應用所屬的釘釘開放平臺帳號內的惟一標識 /// </summary> public string unionid { get; set; } /// <summary> ///釘釘Id /// </summary> public string dingId { get; set; } } #endregion
public sealed class QRUrls { public const string SNS_GET_TOKEN = "https://oapi.dingtalk.com/sns/gettoken"; public const string SNS_GET_PERSISTENT_CODE = "https://oapi.dingtalk.com/sns/get_persistent_code"; public const string SNS_GET_SNS_TOKEN = "https://oapi.dingtalk.com/sns/get_sns_token"; public const string SNS_GET_USER_INFO = "https://oapi.dingtalk.com/sns/getuserinfo"; /// <summary> /// 根據unionid獲取成員的userid /// </summary> public const string USER_GET_USERID_BY_UNIONID = "https://oapi.dingtalk.com/user/getUseridByUnionid"; }QRKeys.cs
public class QRKeys { public const string appid = "appid"; public const string appsecret = "appsecret"; public const string tmp_auth_code = "tmp_auth_code"; public const string sns_token = "sns_token"; public const string unionid = "unionid"; public const string access_token = "access_token"; }ConstVars.cs
public class ConstVars { /// <summary> /// 緩存時間 /// </summary> public const int APP_ACCESS_TOKEN_CACHE_TIME = 5000; }
http://XXX.com/ddqrlogin.aspx?code=9ccba352e7043c3face9da66ddba7a5f&state=STATE
附上ConfigTool.cs代碼
markdown
public class ConfigTool { #region FetchAppId Function /// <summary> /// 獲取AppId /// </summary> /// <returns></returns> public static String FetchAppId() { return FetchValue("appId"); } #endregion #region FetchAppSecret Function /// <summary> /// 獲取appSecret /// </summary> /// <returns></returns> public static String FetchAppSecret() { return FetchValue("appSecret"); } #endregion #region FetchValue Function public static String FetchValue(String key) { String value = ConfigurationManager.AppSettings[key]; if (value == null) { throw new Exception($"{key} is null.請確認配置文件中是否已配置."); } return value; } #endregion }在Web.config上配置appid和appsecrect
<appSettings> <add key="appId" value="XX" /> <add key="appSecret" value="XXXXXXXXXXXXXXXXXXXXXX" /> </appSettings>
歡迎打描左側二維碼打賞。
app
轉載請註明出處。