前端:Angular5.2
后端: 用dgate模拟后端返回数据。(业务需要)
Github地址:https://github.com/DTeam-Top/dgate/blob/master/docs/user_guide.md
创建钉钉微应用及其免登流程请查看钉钉官方文档。
微应用https://oa.dingtalk.com/index.htm#/microApp/microAppList
免登:https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.T3ohnj&treeId=168&articleId=107551&docType=1
一. 准备工作
将dingtalk.js集成到angular5中
1. 下载dingtalk.js 放到src目录下。(见钉钉官网)
2. 在src目录下新建dingtalk.d.ts文件,并声明变量dd以便调用jsapi。
declare var dd: any;
3. 在angular-cli.json文件中“scripts”中添加:
"scripts": [
"dingtalk.js",
...
],
4. 在tsconfig.json文件中添加:
"types": ["dingtalk.d.ts"],
dingtalk.js集成完毕。
配置环境
在environment.ts以及dgate中配置好自己的ip。
为了让手机访问angular项目需要开启ip访问,可以启动的时候:
ng serve --host 0.0.0.0
也可以在package.json中更改“scripts”对应“start”的配置。
新建dingding.ts文件,存放微应用的corpId,secret,agentId以便后面直接读取。
请将手机和电脑位于同一局域网中。
用户数据准备
本地创建一个mock-user.ts文件,以便存放用户信息。主要用于登录,存放用户名,密码,以及dd_userid即可。(dd_userid为通过dd获取到的userid,初始为空。)
二. 项目思路
用户通过钉钉手机端访问自己创建的微应用。首次登录时通过用户名密码登录,然后记录该用户的userid。下次登录时匹配该userid即可。
dingding-backend.service.ts模拟实际项目中后端请求钉钉接口。
dingding-web.service.ts模拟实际项目中前端使用钉钉jsapi。
三. 实现
- 获取access_token:https://oapi.dingtalk.com
/gettoken?corpid=id&corpsecret=secrect (后端)
- 根据access_token获取ticket:https://oapi.dingtalk.com
/get_jsapi_ticket?access_token=ACCESS_TOKE(后端)
- 根据ticket获取签名signature鉴权(后端)
根据官网上https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7386797.0.0.Sfinbc&source=search&treeId=168&articleId=107551&docType=1#s6 计算。
在angular5中签名算法代码如下:
getSign(access_token,ticket:string) {
// signature, nonstr, url, timestamp, CorpId, agentId
var origUrl = decodeURIComponent(window.location.href);
var origUrlObj = url.parse(origUrl);
delete origUrlObj['hash'];
var newUrl = url.format(origUrlObj);
var nonceStr = 'abcdefg';
var timeStamp = new Date().getTime();
var ticket = ticket;
var plain = 'jsapi_ticket=' + ticket +
'&noncestr=' + nonceStr +
'×tamp=' + timeStamp +
'&url=' + newUrl;
var sha1 = CryptoJS.SHA1(plain);
var signature = sha1.toString(CryptoJS.enc.hax)
let config = {
accessToken:access_token,
signature: signature,
nonceStr: nonceStr,
timeStamp: timeStamp,
corpId: dingding.corpId,
agentId: dingding.agentId
};
this.dingding.emit({type: 'config', config: config});
}
其中用到了CryptoJs使用SHA1加密,具体请参考官方文档。
然后通过EventEmitter模拟将config发送到“前端”。
- dingding-web-service.ts获取到condig后,通过JSAPI获取免登code。
ddJSAPI(config){
const $this = this;
dd.config({
agentId: config.agentId,
corpId: config.corpId,
timeStamp: config.timeStamp,
nonceStr: config.nonceStr,
signature: config.signature,
jsApiList: ['device.notification.confirm',
'device.notification.alert',
'device.notification.prompt',
'biz.chat.chooseConversation',
'biz.ding.post','biz.user.get']
});
dd.ready(function() {
dd.runtime.permission.requestAuthCode({
corpId: dingding.corpId,
onSuccess: function(result) {
$this.dingding.emit({type: 'code', code:result.code});
},
onFail : function(err) {}
});
// dd.biz.user.get({
// corpId:"dingbb55d760782ca6d535c2f4657eb6378f",
// onSuccess: function (info) {
// console.log('======.info===');
// console.log(info);
// alert(JSON.stringify(info))
// },
// onFail: function (err) {}
// });
})
}
获取到免登code后传递给“后端”。
可以通过dd.biz.user.get获取用户信息,为了安全起见,官方推荐将code传给后台,由后端来获取用户信息。
- 后端通过免登code获取用户info
获取info:https://oapi.dingtalk.com
/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
返回:
- 通过userid获取详细信息(非必要)
获取详细信息:https://oapi.dingtalk.com
/user/get?access_token=ACCESS_TOKEN&userid=zhangsan
- 获取到userid后即可结合用户名密码进行绑定。
下次该用户访问时即可通过userid确定身份。
调试:
- 下载钉钉开发版,将手机通过usb连接电脑,打开usb调试。
钉钉开发版以及配置:https://open-doc.dingtalk.com/docs/doc.htm?treeId=171&articleId=104908&docType=1
打开chrome,输入chrome://inspect/#devices 进入调试页面。
-
进入微应用。这个时候会出现一个错误:
vendor.bundle.js文件太大。所以启动的时候还需要加上--aot
-
现在应该可以正常访问微应用了,但是请求钉钉接口的时候,会报错:
angular解决跨域的问题是设置反向代理。
(1)在项目下添加proxy.config.json文件(和package.json同级)
(2)添加文件内容:
{
"/gettoken":{
"target": "https://oapi.dingtalk.com",
"secure": "false",
"changeOrigin": true
},
"/get_jsapi_ticket":{
"target": "https://oapi.dingtalk.com",
"secure": "false",
"changeOrigin": true
},
"/user":{
"target": "https://oapi.dingtalk.com",
"secure": "false",
"changeOrigin": true
}
}
(3)修改请求:
由于设置了反向代理,所以请求为:
this.http.get('/gettoken', {params}).subscribe((res:any)=>{
this.access_token = res.access_token;
this.getTicket(res.access_token);
})
启动时加上 --proxy-configproxy.config.json
最终启动命令为:
"start": "ng serve --host 0.0.0.0 --aot --proxy-config proxy.config.json",
更多反向代理信息请查看Angular官方文档。
- 访问钉钉接口后获取到token等数据,但是发现页面并没有出现,chrome控制台报错:‘null’ is not a function
通过排查发现是angular和android浏览器兼容问题。
解决:
将polyfills.ts文件中支持IE浏览器的特性打开:
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
import 'core-js/es6/set';
现在可以看到页面了。
- 免登流程完毕。但是发现angular从dgate获取的数据,
不能主动渲染页面。
解决:
必须手动触发变更检测。
- 导入ChangeDetectorRef
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
- 构造中
constructor(...
, private _ref: ChangeDetectorRef) {
...
this._ref.detach();
- 获取到数据后
this._ref.detectChanges();
更多变更检测信息请查看Angular官方文档。
由于每个component都需要加上变更检测,所以如果项目中component相互之间的结构复杂,那么对项目性能有很大的影响。