第一部分、准备材料🌲
公网环境
老版钉钉扫码中必须要配置一个域名才可以调试,新版支持IP配置调了。我是手机打开热点,电脑连接热点进行调试的,比老版要方便了不少。
查看本机IP地址方法:
如果使用的Windows,执行命令:ipconfig
如果使用的Mac,执行命令:
ifconfig en0
创建一个小程序(企业内应用即可)
这个小程序可以是H5微应用也可以是小程序,但需要是企业内应用。
将创建后的小程序appKey和appSecret
搭建SpringBoot项目
目录结构如下:
注意:resources中的目录结构和文件名一定要和我的一致,不能多不能少,否则会出现启动报错或者找不到文件的问题。
第二部分、环境配置⚙
SpringBoot项目pom.xml
这里我引入了VM模板用来放置扫码页与首页的html代码,引入DingTalk调用三方接口获取扫码用户的基本信息。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringBoot-DDScan</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBoot-DDScan</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- dingding -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>1.2.5</version>
</dependency>
<!-- VM 模板 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>velocity-spring-boot-starter</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringBoot项目application.properties
spring.application.name=dd-scan
server.port=8080
## 小程序AppID和AppSecret(此处填入自己复制的)
qr.appId=xxxx
qr.appSerret=xxxx
# Velocity\u914D\u7F6E\uFF0C\u8BE6\u89C1 http://gitlab.alibaba-inc.com/middleware-container/pandora-boot/wikis/spring-boot-velocity
spring.velocity.resource-loader-path=classpath:/velocity/templates
spring.velocity.toolbox-config-location=/velocity/toolbox.xml
spring.velocity.layout-url=/velocity/layout/default.vm
钉钉开放平台-登录与分享配置回调域名
这里的回调地址就是扫码后跳转的地址。这里的地址与下面login.vm中的window.url 一定要一模一样,否则会扫码会弹出”回调域名配置错误“的提示。
钉钉开放平台-权限管理授权
选择:个人手机号信息、成员信息读权限权限即可
第三部分、代码📚
后端代码
SpringBootDdScanApplication.java
package com.example.springbootddscan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDdScanApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDdScanApplication.class, args);
}
}
MainController.java
package com.example.springbootddscan.controller;
import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
import com.aliyun.dingtalkcontact_1_0.models.GetUserResponse;
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/login")
public class MainController {
@Value("${qr.appId}")
private String qrAppId;
@Value("${qr.appSerret}")
private String qrAppSecret;
@GetMapping("/toLoginPage")
public ModelAndView toLoginPage() {
return new ModelAndView("login");
}
@GetMapping("/scanLogin")
public ModelAndView scanLogin(@RequestParam String authCode) throws Exception {
//获取当前小程序的accesstoken
GetUserTokenResponse userTokenResponse = getUserAccessToken(qrAppId, qrAppSecret, authCode, "authorization_code");
//查询当前用户信息
GetUserResponse me = getUserWithOptions(userTokenResponse.getBody().getAccessToken(), "me");
//获取首页模板
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.addObject("userid",me.getBody().getOpenId());
modelAndView.addObject("userName",me.getBody().getNick());
modelAndView.addObject("userPhone",me.getBody().getMobile());
return modelAndView;
}
public GetUserResponse getUserWithOptions(String accessToken, String unionId) throws Exception {
// 准备请求配置参数
Config config = new Config();
// 设置请求协议
config.protocol = "https";
// 设置请求区域
config.regionId = "central";
// 初始化账号Client
com.aliyun.dingtalkcontact_1_0.Client client = new com.aliyun.dingtalkcontact_1_0.Client(config);
GetUserHeaders getUserHeaders = new GetUserHeaders();
getUserHeaders.xAcsDingtalkAccessToken = accessToken;
return client.getUserWithOptions(unionId, getUserHeaders, new RuntimeOptions());
}
public GetUserTokenResponse getUserAccessToken(String suiteKey, String suiteSecret, String authCode, String grantType) throws Exception {
// 准备请求配置参数
Config config = new Config();
// 设置请求协议
config.protocol = "https";
// 设置请求区域
config.regionId = "central";
// 初始化账号Client
com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
.setClientId(suiteKey)
.setClientSecret(suiteSecret)
.setCode(authCode)
.setGrantType(grantType);
return client.getUserToken(getUserTokenRequest);
}
}
前端代码
login.vm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>钉钉扫码登录</title>
</head>
<body>
<!-- 方案二 ,该方法依赖jquery库-->
<script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"></script>
<script>
<!-- 获取到的IP地址 -->
window.url = 'http://xxx.xx.xxx.xx:8080/login/scanLogin'
<!-- 小程序的appId -->
window.appid = 'xxxx'
</script>
<!-- STEP1:在HTML中添加包裹容器元素 -->
<div id="self_defined_element" class="self-defined-classname"></div>
<style>
/* STEP2:指定这个包裹容器元素的CSS样式,尤其注意宽高的设置 */
.self-defined-classname {
width: 300px;
height: 300px;
}
</style>
<script>
// STEP3:在需要的时候,调用 window.DTFrameLogin 方法构造登录二维码,并处理登录成功或失败的回调。
window.DTFrameLogin(
{
id: 'self_defined_element',
width: 300,
height: 300,
},
{
redirect_uri: encodeURIComponent(window.url),
client_id: window.appid,
scope: 'openid',
response_type: 'code',
state: 'test',
prompt: 'consent',
},
(loginResult) => {
console.log(loginResult)
const {redirectUrl, authCode, state} = loginResult;
// 这里可以直接进行重定向
window.location.href = redirectUrl;
// 也可以在不跳转页面的情况下,使用code进行授权
console.log(authCode);
},
(errorMsg) => {
// 这里一般需要展示登录失败的具体原因
alert(`Login Error: ${errorMsg}`);
},
);
</script>
</body>
</html>
index.vm
<h1>首页</h1>
<h2>userid: ${userid}</h2>
<h2>用户名: ${userName}</h2>
<h2>手机号码:${userPhone}</h2>
其他文件
toolbox.xml
<?xml version="1.0" encoding="UTF-8"?>
<tools>
<data type="number" key="TOOLS_VERSION" value="2.0"/>
<data type="boolean" key="GENERIC_TOOLS_AVAILABLE" value="true"/>
<toolbox scope="application">
<tool class="org.apache.velocity.tools.generic.AlternatorTool"/>
<tool class="org.apache.velocity.tools.generic.ClassTool"/>
<tool class="org.apache.velocity.tools.generic.ComparisonDateTool"/>
<tool class="org.apache.velocity.tools.generic.ConversionTool"/>
<tool class="org.apache.velocity.tools.generic.DisplayTool"/>
<tool class="org.apache.velocity.tools.generic.EscapeTool"/>
<tool class="org.apache.velocity.tools.generic.FieldTool"/>
<tool class="org.apache.velocity.tools.generic.MathTool"/>
<tool class="org.apache.velocity.tools.generic.NumberTool"/>
<tool class="org.apache.velocity.tools.generic.ResourceTool"/>
<tool class="org.apache.velocity.tools.generic.SortTool"/>
<tool class="org.apache.velocity.tools.generic.XmlTool"/>
</toolbox>
<toolbox scope="request">
<tool class="org.apache.velocity.tools.generic.ContextTool"/>
<tool class="org.apache.velocity.tools.generic.LinkTool"/>
<tool class="org.apache.velocity.tools.generic.LoopTool"/>
<tool class="org.apache.velocity.tools.generic.RenderTool"/>
<tool class="org.apache.velocity.tools.view.CookieTool"/>
<tool class="org.apache.velocity.tools.view.ImportTool"/>
<tool class="org.apache.velocity.tools.view.IncludeTool"/>
<tool class="org.apache.velocity.tools.view.PagerTool"/>
<tool class="org.apache.velocity.tools.view.ParameterTool"/>
<tool class="org.apache.velocity.tools.view.ViewContextTool"/>
<!--
This is not directly useable.
<tool class="org.apache.velocity.tools.view.AbstractSearchTool"/>
-->
<!-- move this to request scope -->
<tool class="org.apache.velocity.tools.generic.ResourceTool"/>
<!--
This is not useful in its default form.
But, if it were, it'd be request-scoped.
<tool class="org.apache.velocity.tools.generic.ValueParser"/>
-->
</toolbox>
</tools>
default.vm
$!{screen_content}
第四部分、演示🍓
输入登录地址
http://xxx.xxx.xxx.xxx:8080/login/toLoginPage
这里记住一定要用IP,不能使用localhost或者127.0.0.1,否则即使二维码能出现,扫码完也不会有反应。
使用钉钉扫码
扫完码后钉钉会弹出一个授权页,点击同意即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RC5ajAZU-1670244714399)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d636a914a6144afc965d47a2aa50b409~tplv-k3u1fbpfcp-watermark.image?)]
钉钉回调地址进入首页
第五部分、原理解释
钉钉文档链接:https://open.dingtalk.com/document/orgapp-server/tutorial-obtaining-user-personal-information