├── .gitignore ├── README.md ├── backend ├── lib │ ├── taobao-sdk-java-auto_1479188381469-20210207-source.jar │ └── taobao-sdk-java-auto_1479188381469-20210207.jar ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── aliyun │ │ └── dingtalk │ │ ├── Application.java │ │ ├── config │ │ └── AppConfig.java │ │ ├── constant │ │ └── UrlConstant.java │ │ ├── controller │ │ ├── DingTalkConfig.java │ │ └── DingTalkUserController.java │ │ ├── exception │ │ ├── GlobalExceptionResolver.java │ │ └── InvokeDingTalkException.java │ │ ├── model │ │ └── ServiceResult.java │ │ ├── service │ │ └── DingTalkUserService.java │ │ └── util │ │ └── AccessTokenUtil.java │ └── resources │ ├── application.properties │ └── static │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── static │ ├── css │ ├── main.4d812b7e.chunk.css │ └── main.4d812b7e.chunk.css.map │ └── js │ ├── 2.16c0b005.chunk.js │ ├── 2.16c0b005.chunk.js.LICENSE.txt │ ├── 2.16c0b005.chunk.js.map │ ├── 3.878cd095.chunk.js │ ├── 3.878cd095.chunk.js.map │ ├── main.69756221.chunk.js │ ├── main.69756221.chunk.js.map │ ├── runtime-main.26c91192.js │ └── runtime-main.26c91192.js.map ├── dingBoot-linux.sh ├── dingBoot-mac.sh ├── dingBoot-windows.bat ├── frontend ├── .gitignore ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ └── setupTests.js └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .idea 21 | /*/*.iml 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | #target 28 | /*/target 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 钉钉用户免登Demo 2 | 3 | ## 1. 功能介绍 4 | 5 | “应用免登录”是指当用户点击一个应用时,无需单独手动输入钉钉用户名和密码,应用程序可自动获取当前用户的钉钉身份,通过钉钉的身份登录应用。 6 | 7 | ## 2. 项目结构 8 | 9 | > **frontend**:前端模块,使用react构建,主要功能有:接入钉钉JSAPI获取authCode、展示免登用户信息。 10 | > 11 | > **backend**:后端模块,使用springboot构建,主要功能有:使用authCode获取access_token、使用access_token获取用户信息、使用用户id获取用户详情等。 12 | 13 | 以下为目录结构与部分核心代码文件: 14 | 15 | ``` 16 | . 17 | ├── README.md ----- 说明文档 18 | ├── backend 19 | │ ├── dingBoot-linux.sh ----- 启动脚本(linux) 20 | │ ├── dingBoot-mac.sh ----- 启动脚本(mac) 21 | │ ├── dingBoot-windows.bat ----- 启动脚本(windows) 22 | │ ├── lib ----- 开发SDK包 23 | │   ├── pom.xml 24 | │   └── src 25 | │   └── main 26 | │   ├── java 27 | │   │   └── com 28 | │   │   └── aliyun 29 | │   │   └── dingtalk 30 | │ │ ├── Application.java ----- 启动类 31 | │   │   ├── config 32 | │ │ │ └── AppConfig.java ----- 应用配置类 33 | │   │   ├── constant 34 | │   │   │   └── UrlConstant.java ----- 接口API 35 | │   │   ├── controller 36 | │   │   │   ├── DingTalkConfig.java ----- 获取配置接口 37 | │   │   │   └── DingTalkUserController.java ----- 核心业务接口 38 | │   │   └── service 39 | │   │      └── DingTalkUserService.java ----- 核心业务代码 40 | │   └── resources 41 | │   └── application.properties ----- 应用配置文件 42 | ├── frontend 43 | │   └── src 44 | │   └── App.js ----- 前端展示页面和交互代码 45 | └── pom.xml 46 | ``` 47 | 48 | ## 3. 开发环境准备 49 | 50 | 1. 需要有一个钉钉注册企业,如果没有可以创建:https://oa.dingtalk.com/register_new.htm#/ 51 | 52 | 2. 成为钉钉开发者,参考文档:https://developers.dingtalk.com/document/app/become-a-dingtalk-developer 53 | 54 | 3. 登录钉钉开放平台后台创建一个H5应用: https://open-dev.dingtalk.com/#/index 55 | 56 | 4. 配置应用: 57 | 58 | ① 配置开发管理,参考文档:https://developers.dingtalk.com/document/app/configure-orgapp 59 | 60 | **此处配置“应用首页地址”需公网地址,若无公网ip,可使用钉钉内网穿透工具:** https://developers.dingtalk.com/document/resourcedownload/http-intranet-penetration 61 | 62 | ![img](https://img.alicdn.com/imgextra/i4/O1CN01QGY87t1lOZN65XHqR_!!6000000004809-2-tps-2870-1070.png) 63 | 64 | ② 配置相关权限,参考文档:https://developers.dingtalk.com/document/app/address-book-permissions 65 | 66 | 本demo使用接口权限:"成员信息读权限" 67 | 68 | ![img](https://img.alicdn.com/imgextra/i2/O1CN01n0QZM321k7rcBwfsr_!!6000000007022-2-tps-2822-1080.png) 69 | 70 | ## 4. 启动项目 71 | 72 | ### 4.1 脚本启动(推荐) 73 | 74 | 脚本启动,只需执行一条命令即可启动项目,方便快速体验。 75 | 76 | #### 4.1.1 脚本说明 77 | 78 | ① 脚本启动前置条件: 79 | 80 | - 安装配置了java开发环境(jdk、maven) 81 | - 安装配置了git工具 82 | 83 | ② 脚本类型如下: 84 | 85 | ```shell 86 | dingBoot-linux.sh # linux版本 87 | dingBoot-mac.sh # mac版本 88 | dingBoot-windows.bat # windows版本 89 | ``` 90 | 91 | #### 4.1.2 下载项目 92 | 93 | ```shell 94 | git clone https://github.com/open-dingtalk/h5app-auth-demo.git 95 | ``` 96 | 97 | #### 4.1.3 启动脚本 98 | 99 | 执行时将命令中示例参数替换为**应用参数**,在后端项目(脚本同级目录)下运行命令。 100 | 101 | ① **脚本运行命令:** 102 | 103 | - **执行linux脚本** 104 | 105 | ```shell 106 | ./dingBoot-linux.sh start {项目名} {端口号} {appKey} {appSecret} {agentId} {corpId} 107 | ``` 108 | 109 | - **mac系统(使用终端运行,mac m1芯片暂不支持)** 110 | 111 | ```shell 112 | ./dingBoot-mac.sh start {项目名} {端口号} {appKey} {appSecret} {agentId} {corpId} 113 | ``` 114 | 115 | - **windows系统 使用cmd命令行启动** 116 | 117 | ```shell 118 | ./dingBoot-windows.bat {项目名} {端口号} {appKey} {appSecret} {agentId} {corpId} 119 | ``` 120 | 121 | - **示例(linux脚本执行)** 122 | 123 | ```sh 124 | ./dingBoot-linux.sh start h5-demo 8080 ding1jmkwa4o19bxxxx ua2qNVhleIx14ld6xgoZqtg84EE94sbizRvCimfXrIqYCeyj7b8QvqYxxx 122549400 ding9f50b15bccd1000 125 | ``` 126 | 127 | ② **参数获取方法:** 128 | 129 | - 获取corpId——开发者后台首页:https://open-dev.dingtalk.com/#/index ![](https://img.alicdn.com/imgextra/i2/O1CN01amtWue1l5nAYRc2hd_!!6000000004768-2-tps-1414-321.png) 130 | 131 | - 进入应用开发-企业内部开发-点击进入应用-基础信息-获取appKey、appSecret、agentId ![](https://img.alicdn.com/imgextra/i3/O1CN01Rpfg001aSjEIczA85_!!6000000003329-2-tps-905-464.png) 132 | 133 | #### 4.1.4 启动后配置 134 | 135 | ① **配置访问地址** 136 | 137 | 启动完成会自动生成临时域名,配置方法:进入开发者后台->进入应用->开发管理->应用首页地址和PC端首页地址 138 | 139 | - 生成的域名: ![](https://img.alicdn.com/imgextra/i3/O1CN01lN8Myr1XIFJmlDSWf_!!6000000002900-2-tps-898-510.png) 140 | 141 | - 配置地址: ![](https://img.alicdn.com/imgextra/i1/O1CN01IWleEp1Kw0hX9suby_!!6000000001227-2-tps-1408-489.png) 142 | 143 | ② **发布应用** 144 | 145 | 配置好地址后进入“版本管理与发布页面”,发布应用,发布后即可在PC钉钉或移动钉钉工作台访问应用 146 | 147 | - 发布应用: ![](https://img.alicdn.com/imgextra/i4/O1CN01DTtp4E1qAtfDeGORj_!!6000000005456-2-tps-1414-479.png) 148 | 149 | ### 4.2 手动启动(与脚本启动二选一) 150 | 151 | #### 4.2.1 下载项目 152 | 153 | ```shell 154 | git clone https://github.com/open-dingtalk/h5app-auth-demo.git 155 | ``` 156 | 157 | #### 4.2.2 配置应用参数 158 | 159 | 获取到以下参数,修改后端application.properties 160 | 161 | ```yaml 162 | app: 163 | app_key: ***** 164 | app_secret: ***** 165 | agent_id: ***** 166 | corp_id: ***** 167 | ``` 168 | 169 | 参数获取方法:登录开发者后台 170 | 171 | - 获取corpId——开发者后台首页:https://open-dev.dingtalk.com/#/index ![](https://img.alicdn.com/imgextra/i2/O1CN01amtWue1l5nAYRc2hd_!!6000000004768-2-tps-1414-321.png) 172 | 173 | - 进入应用开发-企业内部开发-点击进入应用-基础信息-获取appKey、appSecret、agentId ![](https://img.alicdn.com/imgextra/i3/O1CN01Rpfg001aSjEIczA85_!!6000000003329-2-tps-905-464.png) 174 | 175 | #### 4.2.3 修改前端页面 176 | 177 | **打开项目,命令行中执行以下命令,编译打包生成build文件**(如果不修改页面,则可跳过此步骤) 178 | 179 | ```shell 180 | cd frontend 181 | npm install 182 | npm run build 183 | ``` 184 | 185 | **将打包好的静态资源文件放入后端**(如果不修改页面,则可跳过此步骤) 186 | 187 | ![image-20210706173224172](https://img.alicdn.com/imgextra/i2/O1CN01QLp1Qw1TCVrPddfjZ_!!6000000002346-2-tps-322-521.png) 188 | 189 | #### 4.2.4 启动项目 190 | 191 | - 启动SpringBoot(运行启动类Application.java) 192 | 193 | - 启动内网穿透工具,配置应用访问链接并发布应用(参考上方“脚本启动” -> “启动后配置”) 194 | 195 | - 移动端/PC端钉钉点击工作台,找到应用,进入应用体验demo 196 | 197 | ## 5. 页面展示 198 | 199 | img 200 | 201 | 202 | ## 6. 参考文档 203 | 204 | 1. H5应用接入JSAPI,文档链接:https://developers.dingtalk.com/document/app/logon-free-process?spm=ding_open_doc.document.0.0.6dbdad29INJCRg#topic-2025319 205 | 2. 获取企业内部应用access_token,文档链接:https://developers.dingtalk.com/document/app/obtain-orgapp-token?spm=ding_open_doc.document.0.0.938247e54bE13v#topic-1936350 206 | 3. 获取用户userId,文档链接:https://developers.dingtalk.com/document/app/obtain-the-userid-of-a-user-by-using-the-log-free?spm=ding_open_doc.document.0.0.5b9077a26qJ9aI#topic-2010561 207 | 4. 获取用户详情,文档链接:https://developers.dingtalk.com/document/app/query-user-details?spm=ding_open_doc.document.0.0.938247e54bE13v#topic-1960047 208 | -------------------------------------------------------------------------------- /backend/lib/taobao-sdk-java-auto_1479188381469-20210207-source.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/backend/lib/taobao-sdk-java-auto_1479188381469-20210207-source.jar -------------------------------------------------------------------------------- /backend/lib/taobao-sdk-java-auto_1479188381469-20210207.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/backend/lib/taobao-sdk-java-auto_1479188381469-20210207.jar -------------------------------------------------------------------------------- /backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.aliyun.dingtalk 8 | backend 9 | 0.0.1-SNAPSHOT 10 | backend project for &dingtalk 11 | 12 | 1.8 13 | 2.4.3 14 | ${project.artifactId} 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | 23 | org.projectlombok 24 | lombok 25 | 1.18.22 26 | true 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-test 31 | test 32 | 33 | 34 | 35 | org.apache.commons 36 | commons-lang3 37 | 38 | 39 | 40 | com.taobao.top 41 | top-api-sdk-dev 42 | dingtalk-SNAPSHOT 43 | system 44 | ${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20210207.jar 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-dependencies 53 | ${spring-boot.version} 54 | pom 55 | import 56 | 57 | 58 | 59 | 60 | 61 | ${applicationName} 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 3.1 67 | 68 | 1.8 69 | 1.8 70 | UTF-8 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-maven-plugin 76 | ${spring-boot.version} 77 | 78 | true 79 | 80 | 81 | 82 | 83 | repackage 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/Application.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @Data 9 | public class AppConfig { 10 | 11 | @Value("${dingtalk.app_key}") 12 | private String appKey; 13 | 14 | @Value("${dingtalk.app_secret}") 15 | private String appSecret; 16 | 17 | @Value("${dingtalk.corp_id}") 18 | private String corpId; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/constant/UrlConstant.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.constant; 2 | 3 | /** 4 | * 钉钉开放接口网关常量 5 | */ 6 | public class UrlConstant { 7 | 8 | /** 9 | * 获取access_token url 10 | */ 11 | public static final String GET_ACCESS_TOKEN_URL = "https://oapi.dingtalk.com/gettoken"; 12 | /** 13 | * 通过免登授权码获取用户信息 url 14 | */ 15 | public static final String GET_USER_INFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/getuserinfo"; 16 | /** 17 | * 根据用户id获取用户详情 url 18 | */ 19 | public static final String USER_GET_URL = "https://oapi.dingtalk.com/topapi/v2/user/get"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/controller/DingTalkConfig.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.controller; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.aliyun.dingtalk.config.AppConfig; 7 | import com.aliyun.dingtalk.model.ServiceResult; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | public class DingTalkConfig { 14 | 15 | @Autowired 16 | private AppConfig appConfig; 17 | 18 | /** 19 | * 获取钉钉配置 20 | * @return 21 | */ 22 | @GetMapping("/config") 23 | public ServiceResult getDingTalkConfig() { 24 | Map configMap = new HashMap<>(); 25 | configMap.put("corpId", appConfig.getCorpId()); 26 | return ServiceResult.getSuccessResult(configMap); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/controller/DingTalkUserController.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.controller; 2 | 3 | import com.aliyun.dingtalk.service.DingTalkUserService; 4 | import com.aliyun.dingtalk.model.ServiceResult; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | 9 | /** 10 | * 钉钉h5企业内部应用DEMO, 实现了根据用户授权码获取用户信息功能 11 | */ 12 | @RestController 13 | public class DingTalkUserController { 14 | 15 | @Autowired 16 | private DingTalkUserService dingTalkUserService; 17 | 18 | /** 19 | * 欢迎页面, 检查后端服务是否启动 20 | * 21 | * @return 22 | */ 23 | @GetMapping("/welcome") 24 | public String welcome() { 25 | return "welcome"; 26 | } 27 | 28 | /** 29 | * 根据免登授权码, 获取登录用户身份 30 | * 31 | * @param authCode 免登授权码 32 | * @return 33 | */ 34 | @GetMapping("/login") 35 | public ServiceResult login(@RequestParam(value = "authCode") String authCode) { 36 | 37 | return ServiceResult.getSuccessResult(dingTalkUserService.getUserInfo(authCode)); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/exception/GlobalExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.exception; 2 | 3 | import com.aliyun.dingtalk.model.ServiceResult; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | @RestControllerAdvice 10 | @Slf4j 11 | public class GlobalExceptionResolver { 12 | 13 | /** 14 | * 处理所有不可知异常 15 | * 16 | * @param e 异常 17 | * @return json结果 18 | */ 19 | @ExceptionHandler(Exception.class) 20 | public ServiceResult handleException(Exception e) { 21 | // 打印异常堆栈信息 22 | log.error(e.getMessage(), e); 23 | return ServiceResult.getFailureResult(HttpStatus.INTERNAL_SERVER_ERROR.value() + "", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); 24 | } 25 | 26 | /** 27 | * 处理api调用异常 28 | * 29 | * @param e 异常 30 | * @return json结果 31 | */ 32 | @ExceptionHandler(InvokeDingTalkException.class) 33 | public ServiceResult handleInvokeDingTalkException(InvokeDingTalkException e) { 34 | // 打印异常堆栈信息 35 | log.error(e.getMessage(), e); 36 | return ServiceResult.getFailureResult(e.getErrCode(), e.getErrMsg()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/exception/InvokeDingTalkException.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.exception; 2 | 3 | /** 4 | * 调用钉钉接口异常时抛出该异常 5 | */ 6 | public class InvokeDingTalkException extends RuntimeException { 7 | private static final long serialVersionUID = -238091758285157331L; 8 | private String errCode; 9 | private String errMsg; 10 | private String subErrCode; 11 | private String subErrMsg; 12 | 13 | public String getSubErrCode() { 14 | return this.subErrCode; 15 | } 16 | 17 | public void setSubErrCode(String subErrCode) { 18 | this.subErrCode = subErrCode; 19 | } 20 | 21 | public String getSubErrMsg() { 22 | return this.subErrMsg; 23 | } 24 | 25 | public void setSubErrMsg(String subErrMsg) { 26 | this.subErrMsg = subErrMsg; 27 | } 28 | 29 | public InvokeDingTalkException() { 30 | } 31 | 32 | public InvokeDingTalkException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | public InvokeDingTalkException(String message) { 37 | super(message); 38 | } 39 | 40 | public InvokeDingTalkException(Throwable cause) { 41 | super(cause); 42 | } 43 | 44 | public InvokeDingTalkException(String errCode, String errMsg) { 45 | super(errCode + ":" + errMsg); 46 | this.errCode = errCode; 47 | this.errMsg = errMsg; 48 | } 49 | 50 | public InvokeDingTalkException(String errCode, String errMsg, String subErrCode, String subErrMsg) { 51 | super(errCode + ":" + errMsg + ":" + subErrCode + ":" + subErrMsg); 52 | this.errCode = errCode; 53 | this.errMsg = errMsg; 54 | this.subErrCode = subErrCode; 55 | this.subErrMsg = subErrMsg; 56 | } 57 | 58 | public String getErrCode() { 59 | return this.errCode; 60 | } 61 | 62 | public String getErrMsg() { 63 | return this.errMsg; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/model/ServiceResult.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.model; 2 | 3 | /** 4 | * RPC服务返回结果 5 | */ 6 | public class ServiceResult { 7 | 8 | private boolean success; 9 | 10 | private String errorCode; 11 | 12 | private String errorMsg; 13 | 14 | private T data; 15 | 16 | public boolean isSuccess() { 17 | return success; 18 | } 19 | 20 | public void setSuccess(boolean success) { 21 | this.success = success; 22 | } 23 | 24 | public String getErrorCode() { 25 | return errorCode; 26 | } 27 | 28 | public void setErrorCode(String errorCode) { 29 | this.errorCode = errorCode; 30 | } 31 | 32 | public String getErrorMsg() { 33 | return errorMsg; 34 | } 35 | 36 | public void setErrorMsg(String errorMsg) { 37 | this.errorMsg = errorMsg; 38 | } 39 | 40 | public T getData() { 41 | return data; 42 | } 43 | 44 | public void setData(T data) { 45 | this.data = data; 46 | } 47 | 48 | public static ServiceResult getSuccessResult(T t) { 49 | ServiceResult result = new ServiceResult<>(); 50 | result.setSuccess(true); 51 | result.setData(t); 52 | 53 | return result; 54 | } 55 | 56 | public static ServiceResult getFailureResult(String code, String msg) { 57 | ServiceResult result = new ServiceResult<>(); 58 | result.setSuccess(false); 59 | result.setErrorCode(code); 60 | result.setErrorMsg(msg); 61 | 62 | return result; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/service/DingTalkUserService.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.service; 2 | 3 | import com.aliyun.dingtalk.constant.UrlConstant; 4 | import com.aliyun.dingtalk.config.AppConfig; 5 | import com.aliyun.dingtalk.exception.InvokeDingTalkException; 6 | import com.aliyun.dingtalk.util.AccessTokenUtil; 7 | import com.dingtalk.api.DefaultDingTalkClient; 8 | import com.dingtalk.api.DingTalkClient; 9 | import com.dingtalk.api.request.OapiV2UserGetRequest; 10 | import com.dingtalk.api.request.OapiV2UserGetuserinfoRequest; 11 | import com.dingtalk.api.response.OapiV2UserGetResponse; 12 | import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse; 13 | import com.taobao.api.ApiException; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Service; 17 | 18 | 19 | /** 20 | * 用户管理 21 | */ 22 | @Slf4j 23 | @Service 24 | public class DingTalkUserService { 25 | 26 | @Autowired 27 | private AppConfig appConfig; 28 | 29 | public OapiV2UserGetResponse.UserGetResponse getUserInfo(String authCode) { 30 | 31 | // 1. 获取access_token 32 | String accessToken = AccessTokenUtil.getAccessToken(appConfig.getAppKey(), appConfig.getAppSecret()); 33 | 34 | // 2. 获取用户ID 35 | String userId = getUserId(authCode, accessToken); 36 | 37 | // 2. 根据用户ID获取用户详情 38 | return getOapiV2UserGetResponseByUserId(userId, accessToken); 39 | 40 | } 41 | 42 | /** 43 | * 根据authCode获取用户ID 44 | * 45 | * @param authCode 46 | * @param accessToken 47 | * @return 48 | */ 49 | private String getUserId(String authCode, String accessToken) { 50 | DingTalkClient client = new DefaultDingTalkClient(UrlConstant.GET_USER_INFO_URL); 51 | OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest(); 52 | req.setCode(authCode); 53 | OapiV2UserGetuserinfoResponse oapiV2UserGetuserinfoResponse; 54 | try { 55 | oapiV2UserGetuserinfoResponse = client.execute(req, accessToken); 56 | if (oapiV2UserGetuserinfoResponse.isSuccess()) { 57 | OapiV2UserGetuserinfoResponse.UserGetByCodeResponse userGetByCodeResponse = oapiV2UserGetuserinfoResponse.getResult(); 58 | return userGetByCodeResponse.getUserid(); 59 | } else { 60 | throw new InvokeDingTalkException(oapiV2UserGetuserinfoResponse.getErrorCode(), oapiV2UserGetuserinfoResponse.getErrmsg()); 61 | } 62 | 63 | } catch (ApiException e) { 64 | // 需要自己处理异常 65 | e.printStackTrace(); 66 | throw new InvokeDingTalkException(e.getErrCode(), e.getErrMsg()); 67 | } 68 | } 69 | 70 | /** 71 | * 根据用户ID获取用户详情 72 | * 73 | * @param userId 74 | * @param accessToken 75 | * @return 76 | */ 77 | private OapiV2UserGetResponse.UserGetResponse getOapiV2UserGetResponseByUserId(String userId, String accessToken) { 78 | DingTalkClient client = new DefaultDingTalkClient(UrlConstant.USER_GET_URL); 79 | OapiV2UserGetRequest req = new OapiV2UserGetRequest(); 80 | req.setUserid(userId); 81 | req.setLanguage("zh_CN"); 82 | 83 | try { 84 | OapiV2UserGetResponse oapiV2UserGetResponse = client.execute(req, accessToken); 85 | if (oapiV2UserGetResponse.isSuccess()) { 86 | return oapiV2UserGetResponse.getResult(); 87 | } else { 88 | throw new InvokeDingTalkException(oapiV2UserGetResponse.getErrorCode(), oapiV2UserGetResponse.getErrmsg()); 89 | } 90 | } catch (ApiException e) { 91 | // 需要自己处理异常 92 | e.printStackTrace(); 93 | throw new InvokeDingTalkException(e.getErrCode(), e.getErrMsg()); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /backend/src/main/java/com/aliyun/dingtalk/util/AccessTokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.aliyun.dingtalk.util; 2 | 3 | import com.aliyun.dingtalk.constant.UrlConstant; 4 | import com.aliyun.dingtalk.exception.InvokeDingTalkException; 5 | import com.dingtalk.api.DefaultDingTalkClient; 6 | import com.dingtalk.api.request.OapiGettokenRequest; 7 | import com.dingtalk.api.response.OapiGettokenResponse; 8 | import com.taobao.api.ApiException; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.http.HttpMethod; 11 | 12 | import java.util.Objects; 13 | 14 | 15 | /** 16 | * 获取access_token工具类 17 | */ 18 | @Slf4j 19 | public class AccessTokenUtil { 20 | 21 | /** 22 | * 在使用accessToken时,请注意: 23 | * accessToken的有效期为7200秒(2小时),有效期内重复获取会返回相同结果并自动续期,过期后获取会返回新的accessToken。 24 | * 开发者需要缓存accessToken,用于后续接口的调用。因为每个应用的accessToken是彼此独立的,所以进行缓存时需要区分应用来进行存储。 25 | * 不能频繁调用接口,否则会受到频率拦截。 26 | * 27 | * @param appKey 28 | * @param appSecret 29 | * @return 30 | */ 31 | public static String getAccessToken(String appKey, String appSecret) { 32 | 33 | DefaultDingTalkClient client = new DefaultDingTalkClient(UrlConstant.GET_ACCESS_TOKEN_URL); 34 | OapiGettokenRequest request = new OapiGettokenRequest(); 35 | 36 | request.setAppkey(appKey); 37 | request.setAppsecret(appSecret); 38 | request.setHttpMethod(HttpMethod.GET.name()); 39 | 40 | try { 41 | OapiGettokenResponse response = client.execute(request); 42 | if (response.isSuccess()) { 43 | return response.getAccessToken(); 44 | } else { 45 | throw new InvokeDingTalkException(response.getErrorCode(), response.getErrmsg()); 46 | } 47 | } catch (ApiException e) { 48 | // 需要自己处理异常 49 | e.printStackTrace(); 50 | throw new InvokeDingTalkException(e.getErrCode(), e.getErrMsg()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=${port} 2 | # 替换为自己应用的appKey 3 | dingtalk.app_key=${appKey} 4 | # 替换为自己应用的appSecret 5 | dingtalk.app_secret=${appSecret} 6 | #corpId 7 | dingtalk.corp_id=${corpId} 8 | -------------------------------------------------------------------------------- /backend/src/main/resources/static/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.4d812b7e.chunk.css", 4 | "main.js": "./static/js/main.69756221.chunk.js", 5 | "main.js.map": "./static/js/main.69756221.chunk.js.map", 6 | "runtime-main.js": "./static/js/runtime-main.26c91192.js", 7 | "runtime-main.js.map": "./static/js/runtime-main.26c91192.js.map", 8 | "static/js/2.16c0b005.chunk.js": "./static/js/2.16c0b005.chunk.js", 9 | "static/js/2.16c0b005.chunk.js.map": "./static/js/2.16c0b005.chunk.js.map", 10 | "static/js/3.878cd095.chunk.js": "./static/js/3.878cd095.chunk.js", 11 | "static/js/3.878cd095.chunk.js.map": "./static/js/3.878cd095.chunk.js.map", 12 | "index.html": "./index.html", 13 | "static/css/main.4d812b7e.chunk.css.map": "./static/css/main.4d812b7e.chunk.css.map", 14 | "static/js/2.16c0b005.chunk.js.LICENSE.txt": "./static/js/2.16c0b005.chunk.js.LICENSE.txt" 15 | }, 16 | "entrypoints": [ 17 | "static/js/runtime-main.26c91192.js", 18 | "static/js/2.16c0b005.chunk.js", 19 | "static/css/main.4d812b7e.chunk.css", 20 | "static/js/main.69756221.chunk.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /backend/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/backend/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /backend/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /backend/src/main/resources/static/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/backend/src/main/resources/static/logo192.png -------------------------------------------------------------------------------- /backend/src/main/resources/static/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/backend/src/main/resources/static/logo512.png -------------------------------------------------------------------------------- /backend/src/main/resources/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/resources/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /backend/src/main/resources/static/static/css/main.4d812b7e.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.App{text-align:center;padding:24px}.avatar{width:48px;height:48px;border-radius:8px;overflow:hidden} 2 | /*# sourceMappingURL=main.4d812b7e.chunk.css.map */ -------------------------------------------------------------------------------- /backend/src/main/resources/static/static/css/main.4d812b7e.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://src/index.css","webpack://src/App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEY,CACZ,kCAAmC,CACnC,iCACF,CAEA,KACE,yEAEF,CCZA,KACE,iBAAkB,CAClB,YACF,CAEA,QACE,UAAW,CACX,WAAY,CACZ,iBAAkB,CAClB,eACF","file":"main.4d812b7e.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n padding: 24px;\n}\n\n.avatar {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n overflow: hidden;\n}\n"]} -------------------------------------------------------------------------------- /backend/src/main/resources/static/static/js/2.16c0b005.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.20.2 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v17.0.2 17 | * react-dom.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.2 26 | * react-jsx-runtime.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.2 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /backend/src/main/resources/static/static/js/3.878cd095.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpfrontend=this.webpackJsonpfrontend||[]).push([[3],{280:function(t,e,n){"use strict";n.r(e),n.d(e,"getCLS",(function(){return p})),n.d(e,"getFCP",(function(){return S})),n.d(e,"getFID",(function(){return F})),n.d(e,"getLCP",(function(){return k})),n.d(e,"getTTFB",(function(){return C}));var i,a,r,o,u=function(t,e){return{name:t,value:void 0===e?-1:e,delta:0,entries:[],id:"v1-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(t,e){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){if("first-input"===t&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(t){return t.getEntries().map(e)}));return n.observe({type:t,buffered:!0}),n}}catch(t){}},f=function(t,e){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(t(i),e&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(t){addEventListener("pageshow",(function(e){e.persisted&&t(e)}),!0)},d="function"==typeof WeakSet?new WeakSet:new Set,m=function(t,e,n){var i;return function(){e.value>=0&&(n||d.has(e)||"hidden"===document.visibilityState)&&(e.delta=e.value-(i||0),(e.delta||void 0===i)&&(i=e.value,t(e)))}},p=function(t,e){var n,i=u("CLS",0),a=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),n())},r=c("layout-shift",a);r&&(n=m(t,i,e),f((function(){r.takeRecords().map(a),n()})),s((function(){i=u("CLS",0),n=m(t,i,e)})))},v=-1,l=function(){return"hidden"===document.visibilityState?0:1/0},h=function(){f((function(t){var e=t.timeStamp;v=e}),!0)},g=function(){return v<0&&(v=l(),h(),s((function(){setTimeout((function(){v=l(),h()}),0)}))),{get timeStamp(){return v}}},S=function(t,e){var n,i=g(),a=u("FCP"),r=function(t){"first-contentful-paint"===t.name&&(f&&f.disconnect(),t.startTime=0&&a1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,e){var n=function(){w(t,e),a()},i=function(){a()},a=function(){removeEventListener("pointerup",n,y),removeEventListener("pointercancel",i,y)};addEventListener("pointerup",n,y),addEventListener("pointercancel",i,y)}(e,t):w(e,t)}},b=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,T,y)}))},F=function(t,e){var n,r=g(),p=u("FID"),v=function(t){t.startTime=0&&(n||u.has(t)||\"hidden\"===document.visibilityState)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},s=function(e,t){var n,i=a(\"CLS\",0),u=function(e){e.hadRecentInput||(i.value+=e.value,i.entries.push(e),n())},s=r(\"layout-shift\",u);s&&(n=f(e,i,t),o((function(){s.takeRecords().map(u),n()})),c((function(){i=a(\"CLS\",0),n=f(e,i,t)})))},m=-1,p=function(){return\"hidden\"===document.visibilityState?0:1/0},v=function(){o((function(e){var t=e.timeStamp;m=t}),!0)},d=function(){return m<0&&(m=p(),v(),c((function(){setTimeout((function(){m=p(),v()}),0)}))),{get timeStamp(){return m}}},l=function(e,t){var n,i=d(),o=a(\"FCP\"),s=function(e){\"first-contentful-paint\"===e.name&&(p&&p.disconnect(),e.startTime=0&&t1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var n=function(){y(e,t),a()},i=function(){a()},a=function(){removeEventListener(\"pointerup\",n,h),removeEventListener(\"pointercancel\",i,h)};addEventListener(\"pointerup\",n,h),addEventListener(\"pointercancel\",i,h)}(t,e):y(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,E,h)}))},L=function(n,s){var m,p=d(),v=a(\"FID\"),l=function(e){e.startTime{\n dd.ready(function () {\n let corpId;\n fetch(domain + '/config')\n .then(res => res.json())\n .then((result) => {\n // alert(JSON.stringify(result));\n corpId = result.data.corpId;\n // dd.ready参数为回调函数,在 环境准备就绪时触发,jsapi的调用需要保证在该回调函数触发后调用,否则无效。\n dd.runtime.permission.requestAuthCode({\n \n corpId: corpId, //三方企业ID\n onSuccess: function(result) {\n axios.get(domain + \"/login?authCode=\" + result.code)\n .then(response => {\n setUserInfo(response?.data?.data)\n // console.log(response)\n })\n .catch(error => {\n alert(JSON.stringify(error))\n // console.log(error.message)\n })\n \n },\n onFail : function(err) {\n alert(JSON.stringify(err))\n }\n });\n });\n });\n },[])\n\nreturn
\n \n

{userInfo.name} 登陆成功

\n
\n}\n\n\n\nexport default App;\n","const reportWebVitals = onPerfEntry => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /backend/src/main/resources/static/static/js/runtime-main.26c91192.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s>>> npm_run_build" 74 | # npm_run_build 75 | echo ">>>> maven_build_package" 76 | maven_build_package 77 | echo ">>>> extract_jar" 78 | extract_jar 79 | java -jar ${APP_NAME} --port=${port} --appKey=${appKey} --appSecret=${appSecret} --agentId=${agentId} --corpId=${corpId} >> Log.log 2>&1 & 80 | echo ">>>> start_pierced" 81 | start_pierced 82 | } 83 | 84 | stop() { 85 | is_exist 86 | if [ $? -eq "0" ]; then 87 | kill -9 $pid 88 | echo "${APP_NAME} already stop !" 89 | else 90 | echo "${APP_NAME} is not running" 91 | fi 92 | rm -rf Log.log 93 | } 94 | 95 | 96 | status() { 97 | is_exist 98 | if [ $? -eq "0" ]; then 99 | echo "${APP_NAME} is running. Pid is ${pid}" 100 | else 101 | echo "${APP_NAME} is not running." 102 | fi 103 | } 104 | 105 | restart() { 106 | stop 107 | start 108 | } 109 | 110 | # shellcheck disable=SC1073 111 | case "$1" in 112 | "start") 113 | start 114 | ;; 115 | "stop") 116 | stop 117 | ;; 118 | "status") 119 | status 120 | ;; 121 | "restart") 122 | restart 123 | ;; 124 | *) 125 | usage 126 | ;; 127 | esac 128 | -------------------------------------------------------------------------------- /dingBoot-mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | appName=$2 3 | port=$3 4 | appKey=$4 5 | appSecret=$5 6 | agentId=$6 7 | corpId=$7 8 | APP_NAME=${appName}.jar 9 | usage() { 10 | echo "Usage:sh $0 [start|stop|restart|status]" 11 | exit 1 12 | } 13 | is_exist() { 14 | pid=`ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}' ` 15 | #If there is no return 1, return 0 16 | if [ -z "${pid}" ]; then 17 | return 1 18 | else 19 | return 0 20 | fi 21 | } 22 | 23 | extract_jar() { 24 | if [ ! -d "backend/target/" ];then 25 | exit 1 26 | else 27 | mv backend/target/*.jar ./${APP_NAME} 28 | rm -rf backend/target/ 29 | fi 30 | 31 | } 32 | 33 | start_pierced() { 34 | cd ../ 35 | if [ ! -d "dingtalk-pierced-client/" ];then 36 | git clone https://github.com/open-dingtalk/dingtalk-pierced-client.git 37 | cd dingtalk-pierced-client/mac 38 | chmod 777 ./ding 39 | ./ding -config=./ding.cfg -subdomain=${appKey} $port 40 | else 41 | cd dingtalk-pierced-client/mac 42 | chmod 777 ./ding 43 | ./ding -config=./ding.cfg -subdomain=${appKey} $port 44 | fi 45 | 46 | } 47 | 48 | #npm_run_build() { 49 | # cd ../frontend 50 | # if [ ! -d "node_modules/" ];then 51 | # npm install 52 | # npm run build 53 | # else 54 | # npm run build 55 | # fi 56 | # cd ../backend 57 | #} 58 | 59 | maven_build_package(){ 60 | mvn clean package 61 | if (( $? )) 62 | then 63 | echo "mvn package failed" 64 | exit 1 65 | else 66 | echo "mvn package success" 67 | fi 68 | } 69 | 70 | #Start method 71 | start() { 72 | stop 73 | # echo ">>>> npm_run_build" 74 | # npm_run_build 75 | echo ">>>> maven_build_package" 76 | maven_build_package 77 | echo ">>>> extract_jar" 78 | extract_jar 79 | java -jar ${APP_NAME} --port=${port} --appKey=${appKey} --appSecret=${appSecret} --agentId=${agentId} --corpId=${corpId} >> Log.log 2>&1 & 80 | echo ">>>> start_pierced" 81 | start_pierced 82 | } 83 | 84 | stop() { 85 | is_exist 86 | if [ $? -eq "0" ]; then 87 | kill -9 $pid 88 | echo "${APP_NAME} already stop !" 89 | else 90 | echo "${APP_NAME} is not running" 91 | fi 92 | rm -rf Log.log 93 | } 94 | 95 | 96 | status() { 97 | is_exist 98 | if [ $? -eq "0" ]; then 99 | echo "${APP_NAME} is running. Pid is ${pid}" 100 | else 101 | echo "${APP_NAME} is not running." 102 | fi 103 | } 104 | 105 | restart() { 106 | stop 107 | start 108 | } 109 | 110 | # shellcheck disable=SC1073 111 | case "$1" in 112 | "start") 113 | start 114 | ;; 115 | "stop") 116 | stop 117 | ;; 118 | "status") 119 | status 120 | ;; 121 | "restart") 122 | restart 123 | ;; 124 | *) 125 | usage 126 | ;; 127 | esac 128 | -------------------------------------------------------------------------------- /dingBoot-windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set appName=%1 3 | set port=%2 4 | set appKey=%3 5 | set appSecret=%4 6 | set agentId=%5 7 | set corpId=%6 8 | 9 | if not exist ../dingtalk-pierced-client ( 10 | git clone https://github.com/open-dingtalk/dingtalk-pierced-client.git ../dingtalk-pierced-client 11 | ) 12 | 13 | start cmd /k "mvn clean package && move backend\target\*.jar %appName%.jar && java -jar %appName%.jar --port=%port% --appKey=%appKey% --appSecret=%appSecret% --agentId=%agentId% --corpId=%corpId%" 14 | start cmd /k "cd ../dingtalk-pierced-client/windows_64 && ding -config=ding.cfg -subdomain=%appKey% %port%" 15 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /package-lock.json 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.13.0", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^0.21.1", 10 | "dingtalk-jsapi": "^2.13.41", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-scripts": "4.0.3", 14 | "web-vitals": "^1.1.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "build-windows": "react-scripts build && xcopy build\\* ..\\backend\\src\\main\\resources\\static\\ /E /Y", 20 | "build-mac": "react-scripts build && cp -R ./build/* ../backend/src/main/resources/static", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "homepage": "." 43 | } 44 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-dingtalk/h5app-auth-demo/093136c5971fcacc560cd7c88b3569b78d501686/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | padding: 24px; 4 | } 5 | 6 | .avatar { 7 | width: 48px; 8 | height: 48px; 9 | border-radius: 8px; 10 | overflow: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import react ,{useEffect,useState} from 'react' 2 | import './App.css'; 3 | import axios from 'axios'; 4 | import * as dd from 'dingtalk-jsapi'; 5 | 6 | //内网穿透工具介绍: 7 | // https://developers.dingtalk.com/document/resourcedownload/http-intranet-penetration?pnamespace=app 8 | // 替换成后端服务域名 9 | const domain = ""; 10 | function App() { 11 | const [userInfo,setUserInfo] = useState({name:'',avatar:""}) 12 | useEffect(()=>{ 13 | dd.ready(function () { 14 | let corpId; 15 | fetch(domain + '/config') 16 | .then(res => res.json()) 17 | .then((result) => { 18 | // alert(JSON.stringify(result)); 19 | corpId = result.data.corpId; 20 | // dd.ready参数为回调函数,在 环境准备就绪时触发,jsapi的调用需要保证在该回调函数触发后调用,否则无效。 21 | dd.runtime.permission.requestAuthCode({ 22 | 23 | corpId: corpId, //三方企业ID 24 | onSuccess: function(result) { 25 | axios.get(domain + "/login?authCode=" + result.code) 26 | .then(response => { 27 | setUserInfo(response?.data?.data) 28 | // console.log(response) 29 | }) 30 | .catch(error => { 31 | alert(JSON.stringify(error)) 32 | // console.log(error.message) 33 | }) 34 | 35 | }, 36 | onFail : function(err) { 37 | alert(JSON.stringify(err)) 38 | } 39 | }); 40 | }); 41 | }); 42 | },[]) 43 | 44 | return
45 | 46 |

{userInfo.name} 登陆成功

47 |
48 | } 49 | 50 | 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.dingtalk 8 | h5app-auth-demo 9 | pom 10 | 1.0.0-SNAPSHOT 11 | 12 | backend 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.0.6.RELEASE 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------