h5应用 vue 钉钉_快速上手——钉钉H5微应用开发接入
创建钉钉H5应用
顾名思义,钉钉H5应用,和微信WEB应用一样,本质都是一个有前端有后端的网站,由平台本身对网站基础功能进行扩充,提供专用接口满足开发者各式各样的和平台相关的需求。开发者平台:https://open-dev.dingtalk.com/先决条件:公司管理员和子管理员权限创建应用的流程很简单,开发者平台里新建一个应用,再为应用配置域名、IP白名单、接口权限等信息即可。
关于免登
免登的关键在于如何识别用户,微信网页也好,微信小程序也好,钉钉也好,都开放了获取用户信息的接口,在这基础上做免登的流程是:向平台获取用户信息 -> 为用户登录。微信网页获取用户信息的流程是:用户同意授权(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}×tamp={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>
最终效果钉钉版:
微信版:
h5应用 vue 钉钉_快速上手——钉钉H5微应用开发接入相关推荐
- 【转】Vue.js 2.0 快速上手精华梳理
Vue.js 2.0 快速上手精华梳理 Sandy 发掘代码技巧:公众号:daimajiqiao 自从Vue2.0发布后,Vue就成了前端领域的热门话题,github也突破了三万的star,那么对于新 ...
- linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一
[快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...
- 程序员小sister的烦恼_快速上手大数据ETL神器Kettle(xls导入mysql)
我正在参加年度博客之星评选,请大家帮我投票打分,您的每一分都是对我的支持与鼓励. 2021年「博客之星」参赛博主:Maynor大数据 https://bbs.csdn.net/topics/60395 ...
- vue nodejs 构建_如何使用nodejs后端打字稿版本开发和构建vue js应用
vue nodejs 构建 There are so many ways we can build Vue.js apps and ship for production. One way is to ...
- h5 bootstrap 小程序模板_软件测试人员必知H5/小程序测试点
最近接触了较多关于H5页面的测试,H5页面的测试除了业务逻辑功能测试外,其他部分的测试方法基本是可以通用的,在此对H5页面和小程序的一些通用测试方法进行总结分享给大家 01 H5相关测试 H5优势: ...
- h5活动是什么意思_浅谈什么是H5页面,怎么制作h5页面
来源|活动盒子-APP活动运营工具(huodonghezi.com) 我们经常在社交平台都可以看到很多h5页面活动,这些h5活动页面不仅做的炫酷,而且还有非常好的营销推广效果.那么,怎么制作h5页面呢 ...
- 快速上手Django(一) 项目结构、开发环境、开发流程、视图、视图集
文章目录 一.Django 基础 1. django 项目的目录结构 2. 开发环境(Pycharm 启动django) 1)先找到mange.py 2)项目配置远程python解释器 3)点击`ed ...
- 如何在钉钉上开发自己的应用_快速上手——钉钉H5微应用开发接入
创建钉钉H5应用 顾名思义,钉钉H5应用,和微信WEB应用一样,本质都是一个有前端有后端的网站,由平台本身对网站基础功能进行扩充,提供专用接口满足开发者各式各样的和平台相关的需求.开发者平台:http ...
- 对接钉钉审批_低代码对接钉钉创建外部联系人
上次做的CRM对接了钉钉的报价审批功能,客户反映发起审批时每次都要填写完整的客户名称,还不能填错,还是不太方便,希望在钉钉上也能选择到白码系统中已有的客户数据. 白码--无代码 | 低代码软件开发w ...
- 傅里叶变换音频可视化_快速上手网易云音乐可视化
作者:Sanpeier https://juejin.im/post/5dd88289e51d4523564243da 什么是音频可视化 音频可视化,顾名思义,就是通过获取音频的波形.频率和其他来自音 ...
最新文章
- 在开源模式下云计算大数据的现状浅析
- Vue.js 事件处理
- TCP连接之报文首部
- AttributeMap类详解
- ./mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such
- python3 新式类_python新式类和旧式类区别
- Web笔记-使用jsonp解决跨域请求(CROS)问题
- Java中多实现接口的一个好处
- 线程池ThreadPoolExcutor的使用
- 使用RPM安装Mysql5.5找不到配置文件My.cnf
- fortran调用MKL函数库中的gemm的fortran95接口计算矩阵相乘
- 固定效应模型VS随机效应模型
- iexplore.exe命令行参数解释
- 山西农业大学计算机科学与技术分数线,山西农业大学计算机科学与技术专业2016年在湖北理科高考录取最低分数线...
- 计算机进入bios,详细教您电脑怎么进入bios
- 和腾讯大牛的技术面谈,面试总结
- Python-实验4
- 第一章 世界的物质性及其发展规律(辩证的唯物论、唯物的辩证法)
- 新计算机分区,全新的电脑怎么给新硬盘分区?
- 三十岁的男人应该有多少存款?
热门文章
- 190309每日一句
- Atitit bootsAtitit bootstrap布局 栅格.docx目录1. 简述container与container-fluid的区别 11.1.1. 在bootstrap中的布局
- Atitit.收银系统pos 以及打印功能的行业标准
- paip.java c++得到当前类,方法名称以及行号
- paip.Answer 3.0 注册功能SQL注入漏洞解决方案
- paip.java桌面开发应用与WEB RIA应用
- (转)对冲基金投身“另类数据”淘金热
- 2022年 品牌新零售将去向何方?
- 春节面对亲友盘问:有对象了吗?月薪5万码农这样回答
- 【优化算法】原子搜索优化算法(ASO)【含Matlab源码 1541期】