文章目录
- 说明
- 技术路线
- 注意
- 操作步骤
- 思路图
- 一、创建钉钉应用
- 二、创建java项目
- 三、创建vue项目(或uniapp项目),npm引入sdk的依赖
- 四、拥有公网域名端口。开发环境可以使用(贝锐花生壳等工具)
- 五、打开钉钉开发者平台,配置钉钉应用的h5公网地址
- 六、打开手机钉钉,即可看到开发的页面
说明
由于钉钉开发文档的内容特别多,虽然介绍已经非常仔细了,当对于那些第一次看这个文档的时候,会有些疑惑。为了避免少走很多弯路,故写下该文章进行技术分享
- 本文主要功能:1、钉钉免登录获取用户信息 2、钉钉获取当前的定位
简单来说,就是在钉钉里面,展示我们编写的手机格式大小的网页页面
技术路线
VUE作为前端开发框架,后端为Springboot项目
可以直接通过npm运行项目或者nginx运行项目
为了方便(只需要部署一个项目),我把vue打包成为静态文件,放置到Springboot的 static 文件中。
注意
1、钉钉开发文档,有时候叫 开发H5微应用,有时候叫 开发网页应用,注意分辨
2、开发过程中,有时候会用到小程序开发者工具,注意看说明书。jsapi接口有时候这个工具用不了,得实际放到钉钉dingtalk才有用
3、目前该分享,只是涉及到网页应用,不涉及小程序应用。要注意分辨
操作步骤
1、获取钉钉的应用(corpId/agentId/appKey/appSecret)。开发环境可以自己注册企业,自己创建钉钉应用(注意配置免密的权限)
2、创建java项目,pom引入钉钉的sdk
3、创建vue项目(或uniapp项目),npm引入sdk的依赖
4、拥有公网域名端口。开发环境可以使用(贝锐花生壳等工具)
5、打开钉钉开发者平台,配置钉钉应用的h5公网地址
6、打开手机钉钉,即可看到开发的页面
思路图
获取免登录
jsapi鉴权获取定位坐标(只有安卓端 或 苹果端有用)
一、创建钉钉应用
注册钉钉企业,打开钉钉开发者平台
https://open-dev.dingtalk.com/
记录下 corpId
创建应用
记录下 agentId、appKey、appSecret
二、创建java项目
POM引入依赖,因为钉钉的接口分为新的接口和旧的接口,目前最新的版本,新接口和旧接口都是可以使用的。所以两个接口的依赖同时引入
参考我上传到 gitee的后端代码
https://gitee.com/chencanzhan/cancan-java-share/tree/master/dingtalk-demo
核心pom文件
<!-- 新的接口 --><dependency><groupId>com.aliyun</groupId><artifactId>dingtalk</artifactId><version>2.1.21</version></dependency><!-- 旧的接口 --><dependency><groupId>com.aliyun</groupId><artifactId>alibaba-dingtalk-service-sdk</artifactId><version>2.0.0</version></dependency>
核心代码
@Service
public class DingH5Service {@Value("${dingtalk.appKey}")private String appKey;@Value("${dingtalk.appSecret}")private String accessKeySecret;public DingUserInfo getUserByCode(String code) {DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest();req.setCode(code);OapiV2UserGetuserinfoResponse rsp = null;try {rsp = client.execute(req, getAccessToken());} catch (Exception e) {throw new RuntimeException(e);}cn.hutool.json.JSONObject entries = JSONUtil.parseObj(rsp.getBody());Integer errcode = entries.getInt("errcode");if(errcode == 0){cn.hutool.json.JSONObject result = entries.getJSONObject("result");DingUserInfo dingUserInfo = new DingUserInfo();dingUserInfo.setAssociatedUnionid(result.getStr("associated_unionid"));String unionid = result.getStr("unionid");dingUserInfo.setUnionid(unionid);String deviceId = result.getStr("device_id");dingUserInfo.setDeviceId(deviceId);dingUserInfo.setSysLevel(result.getInt("sys_level"));String name = result.getStr("name");dingUserInfo.setName(name);dingUserInfo.setSys(result.getBool("sys"));String userid = result.getStr("userid");dingUserInfo.setUserid(userid);return dingUserInfo;}return null;}public String getJsapiTicket() {com.aliyun.dingtalkoauth2_1_0.Client client = null;try {client = createClient();} catch (Exception e) {throw new RuntimeException(e);}try {com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketHeaders createJsapiTicketHeaders = new com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketHeaders();createJsapiTicketHeaders.xAcsDingtalkAccessToken = getAccessToken();CreateJsapiTicketResponse jsapiTicketWithOptions = client.createJsapiTicketWithOptions(createJsapiTicketHeaders, new RuntimeOptions());CreateJsapiTicketResponseBody body = jsapiTicketWithOptions.getBody();return body.getJsapiTicket();} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}}return null;}/*** 创建钉钉客户端* @return* @throws Exception*/public static com.aliyun.dingtalkoauth2_1_0.Client createClient() throws Exception {com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();config.protocol = "https";config.regionId = "central";return new com.aliyun.dingtalkoauth2_1_0.Client(config);}public String getAccessToken() throws Exception {com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();config.protocol = "https";config.regionId = "central";com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest().setAppKey(appKey).setAppSecret(accessKeySecret);try {return client.getAccessToken(getAccessTokenRequest).getBody().getAccessToken();} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}}return null;}}
@RestController
@RequestMapping("/ding-h5")
public class DingH5Controller {@Value("${dingtalk.agentId}")private String agentId;@Value("${dingtalk.corpId}")private String corpId;@Value("${dingtalk.appKey}")private String appKey;@Value("${dingtalk.urlPath}")private String urlPath;@Autowiredprivate DingH5Service dingH5Service;/*** 获取签名* @param dingConfigSignVo* @param request* @return*/@PostMapping("/signAll")public ResponseEntity<Object> signAll(@RequestBody DingConfigSignVo dingConfigSignVo, HttpServletRequest request){String sign = null;String signedUrl = urlPath;String jticket = dingH5Service.getJsapiTicket();dingConfigSignVo.setJsticket(jticket);Map<String, Object> jMap = new HashMap<>();try {sign = DdConfigSign.sign(dingConfigSignVo.getJsticket(),dingConfigSignVo.getNonceStr(),dingConfigSignVo.getTimeStamp(),signedUrl);} catch (Exception e) {throw new RuntimeException(e);}jMap.put("agentId",agentId);jMap.put("corpId",corpId);jMap.put("appKey",appKey);jMap.put("sign",sign);return new ResponseEntity<>(jMap, HttpStatus.OK);}/*** 根据code获取用户信息* @param code* @return*/@GetMapping("/getUserByCode")public ResponseEntity<Object> getUserByCode(String code){DingUserInfo userByCode = dingH5Service.getUserByCode(code);return new ResponseEntity<>(userByCode, HttpStatus.OK);}}
/*** 计算dd.config的签名参数**/
public class DdConfigSign {/*** 计算dd.config的签名参数** @param jsticket 通过微应用appKey获取的jsticket* @param nonceStr 自定义固定字符串* @param timeStamp 当前时间戳* @param url 调用dd.config的当前页面URL* @return* @throws Exception*/public static String sign(String jsticket, String nonceStr, long timeStamp, String url) throws Exception {String plain = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "×tamp=" + String.valueOf(timeStamp)+ "&url=" + decodeUrl(url);try {MessageDigest sha1 = MessageDigest.getInstance("SHA-256");sha1.reset();sha1.update(plain.getBytes("UTF-8"));return byteToHex(sha1.digest());} catch (Exception e) {throw new Exception(e.getMessage());}}// 字节数组转化成十六进制字符串private static String byteToHex(final byte[] hash) {Formatter formatter = new Formatter();for (byte b : hash) {formatter.format("%02x", b);}String result = formatter.toString();formatter.close();return result;}/*** 因为ios端上传递的url是encode过的,android是原始的url。开发者使用的也是原始url,* 所以需要把参数进行一般urlDecode** @param url* @return* @throws Exception*/private static String decodeUrl(String url) throws Exception {URL urler = new URL(url);StringBuilder urlBuffer = new StringBuilder();urlBuffer.append(urler.getProtocol());urlBuffer.append(":");if (urler.getAuthority() != null && urler.getAuthority().length() > 0) {urlBuffer.append("//");urlBuffer.append(urler.getAuthority());}if (urler.getPath() != null) {urlBuffer.append(urler.getPath());}if (urler.getQuery() != null) {urlBuffer.append('?');urlBuffer.append(URLDecoder.decode(urler.getQuery(), "utf-8"));}return urlBuffer.toString();}public static String getRandomStr(int count) {String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";Random random = new Random();StringBuffer sb = new StringBuffer();for (int i = 0; i < count; i++) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}
}
三、创建vue项目(或uniapp项目),npm引入sdk的依赖
1、使用npm安装。
npm install dingtalk-jsapi --save2、加载 dingtalk-jsapi
import * as dd from 'dingtalk-jsapi'; // 此方式为整体加载,也可按需进行加载
完整的代码
<template><el-main><div>用户名:{{name}}</div><div>当前位置:{{rrsss.address}}</div><button @click="handlegetSignAll">测试</button></el-main>
</template><script>import api from '@/api';
import * as dd from 'dingtalk-jsapi'; // 此方式为整体加载,也可按需进行加载export default {data() {return {t1: 0,name: '',agentId: '',appKey: '',corpId: '',sign: '',rrsss: {},}},mounted() {this.handlegetSignAll();},methods: {handlegetSignAll() {this.t1 = Date.now()let params = {nonceStr: 'a',timeStamp: this.t1}api.getSignAll(params).then(res => {if (res && res.status === 200) {this.agentId = res.data.agentIdthis.appKey = res.data.appKeythis.corpId = res.data.corpIdthis.sign = res.data.signthis.setDDConfig();this.getAuthCode();}})},setDDConfig() {/**钉钉鉴权 */dd.config({agentId: this.agentId, // 必填,微应用IDcorpId: this.corpId,//必填,企业IDtimeStamp: this.t1, // 必填,生成签名的时间戳nonceStr: 'a', // 必填,自定义固定字符串。signature: this.sign, // 必填,签名type: 0, //选填。0表示微应用的jsapi,1表示服务窗的jsapi;不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持jsApiList: ['device.geolocation.get'] // 必填,需要使用的jsapi列表,注意:不要带dd。})this.getGeolocation();//该方法必须带上,用来捕获鉴权出现的异常信息,否则不方便排查出现的问题dd.error(function () {console.log("钉钉鉴权失败,无法定位等,请联系管理员,或重新尝试!");})},getGeolocation() {dd.ready(() => {dd.device.geolocation.get({targetAccuracy: 200,coordinate: 1,withReGeocode: true,useCache: false,onSuccess: function (res) {// 调用成功时回调console.log(res)this.rrsss = res},onFail: function (err) {// 调用失败时回调console.log(err)}});})},getAuthCode() {dd.requestAuthCode({corpId: this.corpId,clientId: this.appKey,onSuccess: (result) => {api.getUserInfo({code:result.code}).then(res => {if (res && res.status === 200) {this.name = res.data.name}})},onFail: function () { },});} }
}
</script>
<style scoped>
</style>
四、拥有公网域名端口。开发环境可以使用(贝锐花生壳等工具)
这自己百度,映射到本地端口可以直接通过npm运行项目或者nginx运行项目
为了方便(只需要部署一个项目),我把vue打包成为静态文件,放置到Springboot的 static 文件中。
五、打开钉钉开发者平台,配置钉钉应用的h5公网地址
选择添加应用能力
填写公网域名
同时记得开放权限