釘釘微應用的開發——主前端


釘釘微應用的開發——主前端

經濟基礎決定上層建築。

 

第一步,這個地方如果當做一般的數據請求來看,沒有什么要說的,用jquery或者zepto的ajax請求都可以很快速實現。就說一下我在這個踩的坑,我在使用ajax異步請求的時候,忽略了異步加載然而同步加載不停止的問題。深入這個話題我也還需要去學去實踐,所以只是簡單說明我的問題,后面會提供鏈接去深入學習。下面的代碼,由於我沒實現手機電腦聯調,所以只能退而其次用alert測試,見諒。js在加載的時候,會先全部同步順序加載,但是ajax請求不會影響同步加載,因而,會按照123的alert彈出,而非順序彈出312。由於的我淺薄理解,導致我后面還沒拿到ajax請求到的_config,就開始執行函數DDConfig(_config)配置釘釘,所以一直不彈出任何彈窗。這個問題我開始解決的方案是將這段ajax單獨放在一個script標簽里面,最先引入,然后再配置釘釘鑒權信息,這個在Android上測試時正常的,然而只是僥幸,iOS不買這個賬。所以使用ajax的complete函數,在這里面執行DDConfig(_config),可看源代碼。

var _config = null; // 定義全局變量_config,初始值為null,用來接收API獲取到的簽名信息

var getConfig = $.ajax({

type: 'POST',

url: '獲取企業簽名的API,后台提供',

data: {

agentId: 109243825,

url: '這是你開發微應用頁面的線上地址,一般是由釘釘管理員配置的。',

},

dataType: 'json',

success: function(data){

console.log('---success-post-dingInfo---');

if(data.status){

_config = data.data;

alert('3. API獲取簽名信息:'+JSON.stringify(_config));

// 開始配置釘釘

DDConfig(_config);

}else{

alert('請求信息出錯');

}

},

error: function(data){

console.log(---error-post-dingInfo---);

}

});

alert('1. API請求開始:'+JSON.stringify(getConfig));

alert('2. 全局輸出_config:'+JSON.stringify(_config));

第二步,這里官方給出很詳細的步驟釘釘移動jsapi開發,你需要使用的api放進dd.config的jsApiList里面即可。其實釘釘的jsapi思路是這樣的。引入dingtalk.js(官方文檔有提供)這個js會給你提供一個全局變量dd,你可以在Chrome的控制台打印出來看看是個什么東西,里面可以識別釘釘版本,手機系統,以及提供一個個api。釘釘移動jsapi里面介紹所有的api,分為無需鑒權api和需要鑒權api,無需鑒權api可以再引入dingtalk.js之后全局使用;鑒權api就需要走后端接口以保證安全性,且鑒權通過才可以使用這部分api。思路就是這樣。

問題1:如果你發現你的dd.ready/dd.error都沒執行,那可能是我上一步遇見的問題,即沒開始配置dd.config,卻執行了dd.ready和dd.error,因為dd是全局變量,不受函數和異步限制,所以寫法上面沒有錯,但是就是什么都不反應,很痛苦。還有一個很粗暴的方法去review你的代碼,那就是清空js代碼,不做ajax請求,直接開始釘釘鑒權,即dd.config、dd.ready、dd.error,這個時候你可以先用固定的鑒權信息(agentId,corpId,timeStamp,nonceStr,signature)去配置,這個時候因為不是實時的鑒權信息,所以肯定要直接進dd.error來提示校驗失敗,那么你就應該知道怎么一點點去排查你的錯誤了。

問題2:如果你發現dd.error被執行了,先恭喜你一下,至少你進入釘釘的api了,哈哈哈哈哈哈哈哈。。。

這個時候報錯,說明你的dd.config里面有信息是錯誤的。那就去一個個打印出來檢查。

還有可能是上一步中的url的問題,比如說我的微應用的鏈接是https://open-doc.dingtalk.com/;那么url也必須完全一樣,注意https也不能錯的哦。可看前輩們的問題集錦釘釘開放平台“常見問題常見問題常見問題

第三步,應該不用說了,只要鑒權通過,就可以直接用釘釘api獲取用戶的信息,只是這個信息很簡單,不怎么涉及安全問題。可看源碼。

第四步,題外話。這個免登,是需要你在通過釘釘api拿到authcode之后再去找后端API請求,告訴他你需要免登獲取更多用戶的信息釘釘免登,這些都是涉及到安全性的了,所有涉及安全的問題都要走后端API。這個我也沒做,暫時還沒用到,以后若是開發有坑還會繼續說。釘釘的生態說不上很穩定,但是由於公司用的多,所以很多東西不管是官方還是前輩們都寫的有詳細文檔和代碼,可以多搜搜。

提交操作,可看源碼。
————————————————
版權聲明:本文為CSDN博主「藍色珊瑚礁」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_34125336/article/details/112018023

 

創建釘釘H5應用
顧名思義,釘釘H5應用,和微信WEB應用一樣,本質都是一個有前端有后端的網站,由平台本身對網站基礎功能進行擴充,提供專用接口滿足開發者各式各樣的和平台相關的需求。 開發者平台:https://open-dev.dingtalk.com/ 先決條件:公司管理員和子管理員權限 創建應用的流程很簡單,開發者平台里新建一個應用,再為應用配置域名、IP白名單、接口權限等信息即可。

9665d57e2bc1c00e18ef11e94bed182f.png

fd142ce6d3235529cdcb881e761ac659.png

2dffb4e8cae7e854dad13d255a916441.png

0f194304aea209b71f39702581b77daf.png

75d686bcb60a76bbba5350b529b64102.png

關於免登
免登的關鍵在於如何識別用戶,微信網頁也好,微信小程序也好,釘釘也好,都開放了獲取用戶信息的接口,在這基礎上做免登的流程是:向平台獲取用戶信息 -> 為用戶登錄。 微信網頁獲取用戶信息的流程是:用戶同意授權(scope=snsapi_userinfo時) -> 獲取code -> 通過code換取網頁授權access_token -> 拉取用戶信息。在獲取code時,本質是由微信客戶端刷新頁面,並在URL中添加CODE參數;此外,獲取access_token時,scope參數如果是snsapi_base,可以進行無感知獲取用戶openid,所以只有當需要獲取詳細信息時,才會用scope=snsapi_userinfo來顯示請求授權,其它場景中(不需要獲取用戶信息,或已經獲取了對應openid的用戶信息)只要使用snsapi_base即可。(官方文檔地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html) 釘釘流程與之類似,區別如下:

    微信通過URL傳遞code,釘釘通過JSAPI的dd.runtime.permission.requestAuthCode接口獲取code;

    不需要用戶授權(真正意義上的無感知);

    直接獲取用戶信息而不需要scope字段。

此外,因為平台性質的差異,釘釘的用戶字段包含了豐富的真實個人信息。 簽名校驗

微信的wx.config參數配置,和釘釘H5的dd.config參數配置,不管是校驗流程、簽名參數的參數名和,還是校驗算法都完全一樣。

在整個過程中(包括其它開發步驟里),有一個非常重要的原則需要格外留意:敏感參數絕對不能出現在前端(比如jsapi_ticket、access_token)。

流程如下:

    獲取access_token

    獲取jsapi_ticket

    計算簽名(微信和釘釘均為jsapi_ticket, nonceStr, timeStamp, url)

    將生成簽名的參數nonceStr,timeStamp, url和最終生成的簽名Signature傳到前端,供config接口配置和注冊權限;除了這幾個參數,dd.config還需要用到agentId(即應用ID)、corpId(即公司ID),wx.config需要用到appId,本質上都是用來標識一個應用。

TOKEN的維護

釘釘的Token有一個服務端緩存刷新機制,只要在失效前請求接口,access_token的過期時間會恢復為7200秒,借由這個機制,可以在后台跑一個定時任務,隔一段時間請求一下,就可以保證當前access_token一直有效。
開發、部署流程(與微信WEB應用一樣):

    開發階段

        可以用自己熟悉的環境、熟悉的框架按普通的WEB開發過程進行前后端開發;

        在需要使用功能的前端頁面引入核心JS-SDK;

        通過dd.config接口注入權限驗證配置;

        調用釘釘JSAPI接口時,需要發起者的IP存在於H5應用后台配置的服務器出口IP列表中。

    部署階段

        常規:把網站部署到服務器,配置DNS解析指向網站;

        登入開發者平台,為應用配置應用首頁地址。

關於H5 DEMO
頁面和微信WEB版完全一樣,只有接口調用方式不一樣。 為了便於解析Token、Ticket、GetUser接口的結果,創建專門的類用於反序列化HTTPResponse。

    public class BaseResponse    {        public int errcode { get; set; }        public string errmsg { get; set; }    }    public class TokenResponse : BaseResponse    {        public string access_token { get; set; }    }    public class TicketResponse : BaseResponse    {        public string ticket { get; set; }        public int expires_in { get; set; }    }    public class GetUserBase : BaseResponse    {//多余的屬性用不到        public string userid { get; set; }    }

新增DDUser類,並創建一個對應的WxUser對象,作為網站用戶。出於隱私考慮,Nickname由userid取Hash而來,避免暴露真實ID。

//DDHelper的GetUserInfo方法        public static DDUser GetUserInfo(string code)        {//先借code取userid,再借userid取詳細信息            try            {                string userid = JsonConvert.DeserializeObject(                    ApiGet($"https://oapi.dingtalk.com/user/getuserinfo?access_token={Token}&code={code}")).userid;                string res = ApiGet($"https://oapi.dingtalk.com/user/get?access_token={Token}&userid={userid}");                return JsonConvert.DeserializeObject(res);            }            catch (Exception)            {                return null;            }        }            public class DDUser    {//刪掉了一大堆用不到的屬性        public string userid { get; set; }        public string errmsg { get; set; }        public string avatar { get; set; }        public string name { get; set; }        public WxUser WxUser => new WxUser()        {            Avatar = string.IsNullOrEmpty(avatar) ? "/ding.png" : avatar,            Created = DateTime.Now,            LastUpdate = DateTime.Now,            Message = 0,            Nickname = "Ding-" + (Convert.ToInt64(userid.GetHashCode()) + int.MaxValue).ToString("x2"),            Openid = userid,            X = 10000,            Y = 0        };    }

與微信項目類似,為了方便生成統一的ConfigData,創建一個專門的類,自動生成nonceStr和timeStamp,並在構造函數里直接計算簽名。

//DDHelper的GetTicket方法,獲取jsapi_ticket        static string _ticket = "";        static DateTime ticket_exp;        public static string GetTicket()        {            if (ticket_exp < DateTime.Now || string.IsNullOrEmpty(_ticket))            {                TicketResponse res =                    JsonConvert.DeserializeObject(                        ApiGet($"https://oapi.dingtalk.com/get_jsapi_ticket?access_token={Token}"));                _ticket = res.ticket;                ticket_exp = DateTime.Now.AddSeconds(res.expires_in);            }            return _ticket;        }            public class DDConfigData    {        public string TimeStamp;        public string NonceStr;        public string Signature;        public string Url;        public DDConfigData(string url = "")        {//參數生成以后,直接計算結果            Url = url;            NonceStr = Guid.NewGuid().ToString("N").Substring(0, 16);            TimeStamp = Convert.ToInt64((DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds).ToString();            var data = $"jsapi_ticket={DDHelper.GetTicket()}&noncestr={NonceStr}&timestamp={TimeStamp}&url={url}";            Console.WriteLine(data);            Signature = General.SHA1(data).ToLower();        }    }

DDHelper.GetToken(),定時任務,用於access_token有效期刷新,需要手動觸發一次(比如放到Startup.cs):

        public static void GetToken()        {            //后台任務無限刷新Token            Task.Run(() =>            {                while (true)                {                    try                    {                        string res = ApiGet($"https://oapi.dingtalk.com/gettoken?appkey={AppKey}&appsecret={AppSecret}");                        Token = JsonConvert.DeserializeObject(res).access_token;                        Thread.Sleep(600000);                    }                    catch (Exception ex)                    {                        Console.WriteLine("GETTOKEN ERROR: " + ex.ToString());                        GetToken();                        break;                    }                }            });        }

DDHelper剩余部分

        public static string ApiGet(string url)        {            using WebClient client = new WebClient();            try            {                string res = client.DownloadString(url);                Console.WriteLine($"【APIGET:\r\n{url}\r\nRESULT:\t{res}】");                return res;            }            catch (Exception) { throw; }        }        public static string ApiPost(string url, string content)        {            using WebClient client = new WebClient();            client.Headers["Content-Type"] = "application/json;charset=utf8";            string res =                 Encoding.UTF8.GetString(client.UploadData(url, Encoding.UTF8.GetBytes(content)));            Console.WriteLine($"【APIPOST:\r\n{url}\r\n{content}\r\nRESULT:\t{res}】");            return res;        }

首頁做微調,識別不同瀏覽器並調用不同視圖進行渲染

        public async Task Index()        {            WxUser user = General.GetUser(HttpContext);            if (General.Users.Count(u => u.Openid == user?.Openid) == 0 &&                HttpContext.User.Identity.IsAuthenticated)            {                //用戶登錄狀態還在,但用戶列表里不存在該用戶,直接登出並刷新                //原因是demo環境用戶列表沒有做持久化+開發環境用戶狀態未清空,                //正式環境不會出現這種問題。                await HttpContext.SignOutAsync();                return RedirectToAction("Index");            }            ViewBag.User = user;            switch (General.UA(Request.Headers["User-Agent"]))            {                case UserAgents.Dingtalk:                    return View("IndexDingtalk");                case UserAgents.Wechat:                    return View("IndexWx");                default:                    return Content("BROWSER_NOT_SUPPORTED");            }        }

Action - DDAuth,作為接口使用,前端頁面調用后,通過釘釘接口獲取用戶信息,並在成功后自動登錄。

        public async TaskDDAuth(string code = "")        {            DDUser user = DDHelper.GetUserInfo(code);            if (user.userid is null)            {                return Content("登錄失敗");            }            var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);            WxUser wxuser = user.WxUser;            identity.AddClaim(new Claim(ClaimTypes.Sid, wxuser.Openid));            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)).ConfigureAwait(false);            General.Users.Add(wxuser);            return Content("succ");        }

Action - DDConfig,mime類型為text/javascript,驗證用戶登錄狀態,生成dd.config參數並返回dd.config配置js到前端。

        public ContentResult DDConfig(string url)        {            ContentResult js = new ContentResult            {                ContentType = "text/javascript"            };            if (HttpContext.User.Identity.IsAuthenticated)            {                //string url = Request.Headers["Referer"].FirstOrDefault();                DDConfigData config = new DDConfigData(url);                Console.WriteLine(JsonConvert.SerializeObject(config));                js.Content = "dd.config({" +$"    agentId: '{DDHelper.AgentId}'," +$"    corpId: '{DDHelper.CorpId}'," +$"    timeStamp: '{config.TimeStamp}'," +$"    nonceStr: '{config.NonceStr}'," +$"    signature: '{config.Signature}'," +"    type: 0," +"    jsApiList: [ 'device.geolocation.get' ]" +"});";            }            else            {                js.Content = "var result='BAD_REQUEST.'";            }            return js;        }

前端部分和微信WEB應用幾乎一樣

    <script>        var words=@Html.Raw(JsonConvert.SerializeObject(General.Words))        $.getScript("/Home/DDConfig?url="+encodeURIComponent(window.location.href));        dd.ready(function () {            if ('@(login?"Y":"N")' == 'N')            {                dd.runtime.permission.requestAuthCode({                    corpId: '@DDHelper.CorpId',                    onSuccess: function (result) {                        $.get("/Home/DDAuth?code=" + result.code, function (e) {                            if (e == "succ") {                                window.location.reload();                            }                         });                    },                    onFail: function (err) {                    }                });}        });        dd.error(function (error) {        });        var userlist =@Html.Raw(JsonConvert.SerializeObject(General.Users));        function getusers() {            $.get("/Home/Nearby", function (e) {                $("#users li").remove();                $.each(e, function (i, val) {                    $("#users").append('            

' + '
' + ' ' + ' + val.nickname + '">' + ' ' + '
' + '
' + '
'
+ val.nickname + ' (' + val.distance + ')' + '

'
+ words[val.message] + '' + '
' + '
'); }); }); } function upload(msg) { dd.device.geolocation.get({ targetAccuracy: 200, coordinate: 0, withReGeocode: Boolean, useCache: false, onSuccess: function (res) { $.post("/Home/Upload", { X: res.latitude, Y: res.longitude, Message: msg }, function (e) { if (e == "succ") { window.location.reload(); } }); }, onFail: function (err) { dd.device.notification.alert({ message: JSON.stringify(err), title: "UPLOAD ERROR", buttonName: "OK", onSuccess: function () { }, onFail: function (err) { } }); } }); } getusers();script> 最終效果 釘釘版:

02cb768a13ee109f6837862729ba6280.png
微信版:
————————————————
版權聲明:本文為CSDN博主「宋夢寒」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42527178/article/details/112205479


開發環境

  • Chrome Latest Version
  • iOS釘釘最新版、Android釘釘開發最新版
  • 其實我一直想實現電腦Chrome可以調試手機內部APP,苦於嘗試多次沒有成功,所以還是電腦和手機同時測試,為了開發時間,只能先委屈一下自己了。因為舒適的測試環境也是開發的一個重要先決條件。

開發目的

  • 企業微應用。
  • 產品需求是在手機端釘釘上開發一個微應用,用來給公司員工填寫反饋信息。
  • 產品要求nickname是通過釘釘接口獲取用戶的昵稱,獲取之后不允許用戶修改。這便涉及到釘釘的接口用需要鑒權的dd.config。
  • 目的具體至實現釘釘企業微應用的鑒權,獲取簡單的用戶信息,順便提一下免登陸。
    頁面以及控制台
  • 看完頁面圖會發現,這個需求簡直算是前端開發里面最簡單的需求了吧。實際上也是很簡單,如果不是去第一次開發釘釘微應用的話。哈哈哈哈哈哈哈哈哈。。。。。

開發進度

  • 頁面布局、樣式按下不表。
  • 頁面需要后端人員接進釘釘頁面,即進度從打開釘釘微應用能夠進來這個頁面開始。

開發思路

  1. 首先,借用公司的agentId和微應用的url(一般由你司企業釘釘管理員提供)通過后台提供的API接口獲取到實時的鑒權信息(agentId,corpId,timeStamp,nonceStr,signature);
  2. 然后,用這個鑒權信息區配置釘釘api接口的dd.config,然后去操作釘釘部分需要安全鑒定的api;
  3. 之后,用釘釘api的biz.user.get獲取用戶信息;
  4. (題外)免登需要dd.runtime.permission.requestAuthCode先獲取authCode,然后去請求后台提供的API,由后台返回更加安全的用戶信息;
  5. 最后,拿到用戶信息之后,將nickname賦值進輸入框,然后提交給后台即可。
    官方的思路圖

嘿嘿,借用官方爸爸的微應用開發思路圖,真的很一目了然,從五個鑒權信息開始都是前端的操作了哦。

DEMO

開發步驟

  1. 第一步,這個地方如果當做一般的數據請求來看,沒有什么要說的,用jquery或者zepto的ajax請求都可以很快速實現。就說一下我在這個踩的坑,我在使用ajax異步請求的時候,忽略了異步加載然而同步加載不停止的問題。深入這個話題我也還需要去學去實踐,所以只是簡單說明我的問題,后面會提供鏈接去深入學習。下面的代碼,由於我沒實現手機電腦聯調,所以只能退而其次用alert測試,見諒。js在加載的時候,會先全部同步順序加載,但是ajax請求不會影響同步加載,因而,會按照123的alert彈出,而非順序彈出312。由於的我淺薄理解,導致我后面還沒拿到ajax請求到的_config,就開始執行函數DDConfig(_config)配置釘釘,所以一直不彈出任何彈窗。這個問題我開始解決的方案是將這段ajax單獨放在一個script標簽里面,最先引入,然后再配置釘釘鑒權信息,這個在Android上測試時正常的,然而只是僥幸,iOS不買這個賬。所以使用ajax的complete函數,在這里面執行DDConfig(_config),可看源代碼。

    var _config = null; // 定義全局變量_config,初始值為null,用來接收API獲取到的簽名信息 var getConfig = $.ajax({ type: 'POST', url: '獲取企業簽名的API,后台提供', data: { agentId: 109243825, url: '這是你開發微應用頁面的線上地址,一般是由釘釘管理員配置的。', }, dataType: 'json', success: function(data){ console.log('---success-post-dingInfo---'); if(data.status){ _config = data.data; alert('3. API獲取簽名信息:'+JSON.stringify(_config)); // 開始配置釘釘 DDConfig(_config); }else{ alert('請求信息出錯'); } }, error: function(data){ console.log(---error-post-dingInfo---); } }); alert('1. API請求開始:'+JSON.stringify(getConfig)); alert('2. 全局輸出_config:'+JSON.stringify(_config));
  2. 第二步,這里官方給出很詳細的步驟釘釘移動jsapi開發,你需要使用的api放進dd.config的jsApiList里面即可。其實釘釘的jsapi思路是這樣的。引入dingtalk.js(官方文檔有提供)這個js會給你提供一個全局變量dd,你可以在Chrome的控制台打印出來看看是個什么東西,里面可以識別釘釘版本,手機系統,以及提供一個個api。釘釘移動jsapi里面介紹所有的api,分為無需鑒權api和需要鑒權api,無需鑒權api可以再引入dingtalk.js之后全局使用;鑒權api就需要走后端接口以保證安全性,且鑒權通過才可以使用這部分api。思路就是這樣。

    • 問題1:如果你發現你的dd.ready/dd.error都沒執行,那可能是我上一步遇見的問題,即沒開始配置dd.config,卻執行了dd.ready和dd.error,因為dd是全局變量,不受函數和異步限制,所以寫法上面沒有錯,但是就是什么都不反應,很痛苦。還有一個很粗暴的方法去review你的代碼,那就是清空js代碼,不做ajax請求,直接開始釘釘鑒權,即dd.config、dd.ready、dd.error,這個時候你可以先用固定的鑒權信息(agentId,corpId,timeStamp,nonceStr,signature)去配置,這個時候因為不是實時的鑒權信息,所以肯定要直接進dd.error來提示校驗失敗,那么你就應該知道怎么一點點去排查你的錯誤了。
    • 問題2:如果你發現dd.error被執行了,先恭喜你一下,至少你進入釘釘的api了,哈哈哈哈哈哈哈哈。。。

  3. 第三步,應該不用說了,只要鑒權通過,就可以直接用釘釘api獲取用戶的信息,只是這個信息很簡單,不怎么涉及安全問題。可看源碼。
  4. 第四步,題外話。這個免登,是需要你在通過釘釘api拿到authcode之后再去找后端API請求,告訴他你需要免登獲取更多用戶的信息釘釘免登,這些都是涉及到安全性的了,所有涉及安全的問題都要走后端API。這個我也沒做,暫時還沒用到,以后若是開發有坑還會繼續說。釘釘的生態說不上很穩定,但是由於公司用的多,所以很多東西不管是官方還是前輩們都寫的有詳細文檔和代碼,可以多搜搜。
  5. 提交操作,可看源碼。

最最最坑

  • 這次最大的坑是我對異步同步的理解不夠到位;
  • 說實話,所有的坑,都還是源於基礎。開篇即說“經濟基礎決定上層建築”,尤其是技術上,基礎決定你未來的路可以走多寬多遠多一馬平川而不至於坑坑窪窪。

推薦知識


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM