钉钉H5微应用Springboot+Vue开发分享

embedded/2024/10/19 7:28:43/

文章目录

  • 说明
    • 技术路线
    • 注意
    • 操作步骤
    • 思路图
  • 一、创建钉钉应用
  • 二、创建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 + "&timestamp=" + 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公网地址

选择添加应用能力

在这里插入图片描述

填写公网域名

在这里插入图片描述

同时记得开放权限

在这里插入图片描述

六、打开手机钉钉,即可看到开发的页面

在这里插入图片描述


http://www.ppmy.cn/embedded/120064.html

相关文章

uniapp在线打包的ios后调用摄像头失败的解决方法

uniapp在线打包的ios后调用摄像头失败的解决方法 解决方法&#xff1a; 由于未选中打包模块的配置 当你在测试时发现能够正常的开启摄像头&#xff0c;但是当你对其进行在线打包后&#xff0c;发现当你点击启用摄像头时&#xff0c;没有反应&#xff0c;或者是打开是黑屏状态…

9.30学习

1.如何保证顺序消费 ●单 topic&#xff0c;单partition&#xff0c;单 consumer&#xff0c;单线程消费&#xff0c;吞吐量低&#xff0c;不推荐 ●如只需保证单key有序&#xff0c;为每个key申请单独内存 queue&#xff0c;每个线程分别消费一个内存 queue 即可&#xff0c…

【RabbitMQ 项目】服务端:服务器模块

文章目录 一.编写思路二.代码实践三.服务端模块关系总结 一.编写思路 成员变量&#xff1a; muduo 库中的 TCP 服务器EventLoop 对象&#xff1a;用于主线程循环监控连接事件协议处理句柄分发器&#xff1a;用于初始化协议处理器&#xff0c;便于把不同请求派发给不同的业务处理…

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网 统版本&#xff1a;官方软件作者&#xff1a;热心网友rom大小&#xff1a;911MB发布日期&#xff1a;2018-12-23 努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂RO…

华为源NAT技术与目的NAT技术

1&#xff09;源NAT对报文源地址进行转换&#xff0c;分为NAT NO-PAT&#xff0c;NAPT,EASY-IP,三元组NAT&#xff1b; &#xff08;1&#xff09;NAT NO-PAT原理&#xff1a; no-port address translation:非端口地址转换&#xff1a;只转换地址&#xff0c;不转换端口&…

【数据库】深入解析 MongoDB 数据库语法

MongoDB 是一种 NoSQL 数据库&#xff0c;采用文档存储模型&#xff0c;特别适合处理大规模数据和高并发请求。与传统的关系型数据库不同&#xff0c;MongoDB 使用灵活的 JSON 样式文档存储数据&#xff0c;允许动态模式和复杂的数据结构。本文将探讨 MongoDB 的基本语法、查询…

第 2 章:Vue 组件化编程

1. 模块与组件、模块化与组件化 1.1 模块 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件为什么: js 文件很多很复杂作用: 复用 js, 简化 js 的编写, 提高 js 运行效率 1.2 组件 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image……)为什么: 一个…

Netty 与 WebSocket之间的关系

WebSocketProtocolHandler 和 Netty 在处理 WebSocket 连接时扮演不同的角色&#xff0c;但它们通常是一起使用的&#xff0c;尤其是在基于 Netty 的项目中。为了更好地理解它们之间的区别&#xff0c;我们首先需要了解 WebSocket 和 Netty 的基本概念。 WebSocket WebSocket…