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

news2024/12/24 11:26:42

文章目录

  • 说明
    • 技术路线
    • 注意
    • 操作步骤
    • 思路图
  • 一、创建钉钉应用
  • 二、创建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;

    @Autowired
    private 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 --save

2、加载 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.agentId
					this.appKey = res.data.appKey
					this.corpId = res.data.corpId
					this.sign = res.data.sign
					this.setDDConfig();
					this.getAuthCode();
				}
			})
		},
		setDDConfig() {
			/**钉钉鉴权 */
			dd.config({
				agentId: this.agentId, // 必填,微应用ID
				corpId: this.corpId,//必填,企业ID
				timeStamp: 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.coloradmin.cn/o/2180847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【YashanDB知识库】客户端字符集与数据库字符集兼容问题

本文转自YashanDB官网&#xff0c;具体内容请见https://www.yashandb.com/newsinfo/7352675.html?templateId1718516 问题现象 客户端yasql配置字符集为GBK&#xff0c;服务端yasdb配置字符集为UTF8&#xff0c;之后执行语句&#xff1a; 会发现&#xff1a; 期望是两个都…

【LeetCode】每日一题 2024_9_29 买票需要的时间(模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 昨天的每日一题是线段树二分&#xff0c;题目难度远超我的能力范围&#xff0c;所以更不出来了 题目&#xff1a;买票需要的时间 代码与解题思路 func timeRequiredToBuy(tickets []int, k int) (sum in…

【Kubernetes知识点】 解读 Service 和 EndpointSlice 之间的关系

【Kubernetes知识点】 解读 Service 和 EndpointSlice 之间的关系 目录 1 概念 1.1 Service的概念1.2 Endpoint 的概念1.3 EndpointSlice 的引入 1.3.1 EndpointSlice支持的地址1.3.2 EndpointSlice的状态1.3.3 EndpointSlice的拓扑信息 1.4 Service 、Endpoint和 EndpointSl…

Beyond Compare 比较CRC值、二进制比较、关联规则比较,有何区别?(CRC比较、CRC值比较)

文章目录 Beyond Compare文件比较方法深入分析CRC值比较定义及工作原理应用场景优点和缺点 二进制比较定义及工作原理应用场景优点和缺点 关联规则比较定义及工作原理应用场景优点和缺点 比较示例 性能差异CRC值比较的性能影响优点缺点 二进制比较的性能影响优点缺点 关联规则比…

C项目--带权限的图书管理系统(1000多行代码,代码数据可下载,极其适合初学练手)

本专栏目的 更新C/C的相关的项目 前言 C语言的图书权限管理系统完结(进阶的一点后面更新)&#xff0c;1000多行代码(核心代码5、600行&#xff09;&#xff1b;本设计是一个比较综合的练习&#xff0c;用到数据结构&#xff08;顺序表、链表、静态链表&#xff09;、文件、排…

发布-订阅模式演示示例

<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>发布-订阅模式示例</title><styl…

LC记录一:寻找旋转数组最小值、判断旋转数组是否存在给定元素

文章目录 33.搜索旋转排序数组81.搜索旋转排序数组||153.寻找旋转排序数组中的最小值154.寻找旋转排序数组中的最小值||参考链接 33.搜索旋转排序数组 https://leetcode.cn/problems/search-in-rotated-sorted-array/description/ 下面这张图片是LC154题官方题解提供的一个图…

重磅!25年3月起,PMP®考试将启用新教材!

近期&#xff0c;PMI对各科目教材进行了调整。9月27日宣布了2025年3月将会更新ACP的考试内容&#xff0c;新版考试仍将围绕敏捷思维和产品交付&#xff0c;但考试内容大纲(ECO)将整合为四大领域&#xff08;思维、领导力、产品、交付&#xff09;&#xff0c;融合现代新的项目类…

DAY18||530.二叉搜索树的最小绝对值差 |501.二叉搜索树中的众数| 236.二叉树的最近公共祖先

530.二叉搜索树的最小绝对值差 题目&#xff1a;530. 二叉搜索树的最小绝对差 - 力扣&#xff08;LeetCode&#xff09; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 …

MongoDB 快速入门+单机部署(附带脚本)

目录 介绍 体系结构 数据模型 BSON BSON 数据类型 特点 高性能 高可用 高扩展 丰富的查询支持 其他特点 部署 单机部署 普通安装 脚本安装 Docker Compose 安装 卸载 停止 MongoDB 删除包 删除数据目录 参考&#xff1a; https://docs.mongoing.com/ 介绍…

Ping到底干了啥?ICMP 协议详解

什么是 ICMP&#xff1f; ICMP&#xff08;Internet Control Message Protocol&#xff0c;互联网控制消息协议&#xff09;是一种网络层协议&#xff0c;主要用于在网络设备之间传递控制信息和错误消息。它是 IP 协议族的一部分&#xff0c;通常与 IP 协议一起使用。ICMP 的主…

cheese自动化平台开发环境搭建【图文版】

目的 cheese自动化平台是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务&#xff0c;节省大量人工操作的时间。可以采用Vscode、IDEA编写&#xff0c;支持Java、Python、nodejs、GO、Rust、Lua。官方提供了视频版教程&#xff0c;对于…

leetcode每日一题day20(24.9.30)——座位预约管理系统

思路&#xff1a;由于一直是出最小的编号&#xff0c;并且除此之外只有添加元素的操作&#xff0c;如果使用数组存储&#xff0c;并记录&#xff0c;这样出最小编号时间是O(n)复杂度,释放一个座位则是O(1)在操作出线机会均等的情况下&#xff0c;平均是O(n/2), 但对于这种重复 …

CANoe_trace介绍以及如何使用C#仿制trace方案介绍

C#UI界面仿制trace界面介绍--初次制作&#xff0c;后续待完善 在汽车电子开发中&#xff0c;DBC&#xff08;Database Container&#xff09;文件对于定义和描述CAN&#xff08;Controller Area Network&#xff09;通信协议至关重要。随着项目的迭代和功能的扩展&#xff0c;手…

Elasticsearch 开放推理 API 增加了对 Google AI Studio 的支持

作者&#xff1a;来自 Elastic Jeff Vestal 我们很高兴地宣布 Elasticsearch 的开放推理 API 支持 Gemini 开发者 API。使用 Google AI Studio 时&#xff0c;开发者现在可以与 Elasticsearch 索引中的数据进行聊天、运行实验并使用 Google Cloud 的模型&#xff08;例如 Gemin…

0基础学前端 day7

大家好&#xff0c;欢迎来到无限大的频道 今天继续带领大家来了解前端的知识&#xff0c;深入了解互联网和浏览器背后的技术。 历史背景 互联网的起源可以追溯到20世纪60年代的ARPANET项目&#xff0c;作为研究机构之间的通信网络。最初的网页浏览器由Tim Berners-Lee于1990…

【工程测试技术】第3章 测试装置的基本特性,静态特性和动态特性,一阶二阶系统的特性,负载效应,抗干扰性

目录 3.1 概述 1测量装置的静态特性 2.标准和标准传递 3.测量装置的动态特性 4.测量装置的负载特性 5.测量装置的抗干扰性 1.线性度 2.灵敏度 3.回程误差 4.分辨力 5.零点漂移和灵敏度漂移 3.3.1 动态特性的数学描述 1.传递函数 2.频率响应函数 3.脉冲响应函数 …

jmeter进行性能测试实践

设置场景接口 一、通过抓取一个场景的接口&#xff08;抓包&#xff09; 自己抓取需要的接口&#xff0c;进行依赖 流程&#xff1a;1.在网页上F12抓取登录页面和登出页面的URL。2.在jemeter设置线程组&#xff0c;添加http请求输入URL等。3.查看结果数 二、通过boday录制 …

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-29

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-29 在这一期中&#xff0c;我们对大语言模型在软件开发中的跨学科应用的几个工作做简要的介绍。相关内容涵盖软件测试时的问题报告&#xff0c;问题分类&#xff0c;测试生成&#xff0c;和软件测试中的AI应用: …

【宝藏篇】加密软件有哪些?10款好用的加密软件推荐!

小明&#xff1a;嘿&#xff0c;小华&#xff0c;你知道有哪些好用的加密软件吗&#xff1f;我最近需要保护一些敏感数据。 小华&#xff1a;当然&#xff0c;小明&#xff01;现在市场上有很多优秀的加密软件&#xff0c;可以帮助你保护数据安全。我正好有10款宝藏级的加密软件…