24282 2 ani în urmă
părinte
comite
3941499d0f

+ 5 - 0
hx-admin/pom.xml

@@ -79,6 +79,11 @@
             <artifactId>hx-victoriatourist</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.fjhx</groupId>
+            <artifactId>hx-dingding</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 49 - 0
hx-dingding/pom.xml

@@ -0,0 +1,49 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.fjhx</groupId>
+        <artifactId>bytesailing</artifactId>
+        <version>1.0</version>
+    </parent>
+
+    <artifactId>hx-dingding</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.fjhx</groupId>
+            <artifactId>hx-base</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dingtalk</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 32 - 0
hx-dingding/src/main/java/com/fjhx/constant/Constant.java

@@ -0,0 +1,32 @@
+package com.fjhx.constant;
+
+/**
+ * https://open-dev.dingtalk.com/fe/app#/appMgr/provider/h5/131175/17
+ * https://open.dingtalk.com/document/isvapp/configure-synchttp-push
+ * https://open.dingtalk.com/document/isvapp/application-development-process-of-third-party-enterprises
+ */
+public class Constant {
+
+    /**
+     * 应用的SuiteKey,登录开发者后台,点击应用管理,进入应用详情可见
+     */
+    public static final String SUITE_KEY = "suite8j0xog63udtsaq7g";
+
+    /**
+     * 应用的SuiteSecret,登录开发者后台,点击应用管理,进入应用详情可见
+     */
+    public static final String SUITE_SECRET = "NdbWhD6Iu9n5h-3IKXeepmPwmcFbOw9Em7UeiJKyq3_Wa8LgmJ-G6b8SmsOJMrMc";
+
+    /**
+     * 回调URL签名用。应用的签名Token, 登录开发者后台,点击应用管理,进入应用详情可见
+     * https://open-dev.dingtalk.com/fe/app#/appMgr/provider/h5/131175/17
+     */
+    public static final String TOKEN = "SJE6L77NzUB4b2HqsY2ELpRg5uD9HJn6dQ";
+
+    /**
+     * 回调URL加解密用。应用的"数据加密密钥",登录开发者后台,点击应用管理,进入应用详情可见
+     * https://open-dev.dingtalk.com/fe/app#/appMgr/provider/h5/131175/17
+     */
+    public static final String ENCODING_AES_KEY = "FFTCrWbBbdDauMGImmno7g6RSFKDF6HllywGl932b8o";
+
+}

+ 91 - 0
hx-dingding/src/main/java/com/fjhx/controller/DingController.java

@@ -0,0 +1,91 @@
+package com.fjhx.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
+import com.fjhx.constant.Constant;
+import com.ruoyi.common.annotation.NonInterception;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+@Slf4j
+@RestController
+@RequestMapping("/open")
+public class DingController {
+
+    /**
+     * 创建应用,验证回调URL创建有效事件(第一次保存回调URL之前)
+     */
+    private static final String EVENT_CHECK_CREATE_SUITE_URL = "check_create_suite_url";
+
+    /**
+     * 创建应用,验证回调URL变更有效事件(第一次保存回调URL之后)
+     */
+    private static final String EVENT_CHECK_UPADTE_SUITE_URL = "check_update_suite_url";
+
+    /**
+     * suite_ticket推送事件
+     */
+    private static final String EVENT_SUITE_TICKET = "suite_ticket";
+
+    /**
+     * 企业授权开通应用事件
+     */
+    private static final String EVENT_TMP_AUTH_CODE = "tmp_auth_code";
+
+    @NonInterception
+    @PostMapping(value = "/dingCallback")
+    public Object dingCallback(
+            @RequestParam(value = "signature") String signature,
+            @RequestParam(value = "timestamp") Long timestamp,
+            @RequestParam(value = "nonce") String nonce,
+            @RequestBody(required = false) JSONObject body) {
+
+        String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;
+
+        try {
+            log.info("begin callback:" + params);
+            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(Constant.TOKEN, Constant.ENCODING_AES_KEY, Constant.SUITE_KEY);
+
+            // 从post请求的body中获取回调信息的加密数据进行解密处理
+            String encrypt = body.getString("encrypt");
+            String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
+            JSONObject callBackContent = JSON.parseObject(plainText);
+
+            log.info("callBackContent:{}", callBackContent.toJSONString());
+
+            // 根据回调事件类型做不同的业务处理
+            String eventType = callBackContent.getString("EventType");
+            switch (eventType) {
+                case EVENT_CHECK_CREATE_SUITE_URL:
+                    log.info("验证新创建的回调URL有效性: " + plainText);
+                    break;
+                case EVENT_CHECK_UPADTE_SUITE_URL:
+                    log.info("验证更新回调URL有效性: " + plainText);
+                    break;
+                case EVENT_SUITE_TICKET:
+                    // suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证),需要保存到应用的db。
+                    // 钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。
+                    // 应用在获取到新的时值时,保存db成功后,返回给钉钉success加密串(如本demo的return)
+                    log.info("应用suite_ticket数据推送: " + plainText);
+                    break;
+                case EVENT_TMP_AUTH_CODE:
+                    // 本事件应用应该异步进行授权开通企业的初始化,目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验
+                    // 即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息,进而初始化开通及企业。
+                    log.info("企业授权开通应用事件: " + plainText);
+                    break;
+                default:
+                    // 其他类型事件处理
+                    log.info("其他类型事件: " + plainText);
+            }
+
+            // 返回success的加密信息表示回调处理成功
+            return dingTalkEncryptor.getEncryptedMap("success", timestamp, nonce);
+        } catch (Exception e) {
+            // 失败的情况,应用的开发者应该通过告警感知,并干预修复
+            log.error("process callback fail." + params, e);
+            return "fail";
+        }
+
+    }
+}

+ 131 - 0
hx-dingding/src/main/java/com/fjhx/utils/DingUtil.java

@@ -0,0 +1,131 @@
+package com.fjhx.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
+import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody;
+import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest;
+import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponseBody;
+import com.aliyun.tea.TeaException;
+import com.aliyun.teaopenapi.models.Config;
+import com.aliyun.teautil.models.RuntimeOptions;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiV2UserGetuserinfoRequest;
+import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
+import com.ruoyi.common.exception.ServiceException;
+import com.taobao.api.ApiException;
+import lombok.extern.slf4j.Slf4j;
+
+
+/**
+ * 钉钉第三方企业应用 - h5微应用
+ *
+ * <p>
+ * <a href="https://open.dingtalk.com/document/isvapp/basic-concepts"> 基础概念 </a>
+ * <a href="https://open.dingtalk.com/document/isvapp/api-overview"> 钉钉api总览 </a>
+ * <a href="https://open.dingtalk.com/document/isvapp/obtain-identity-credentials"> 获取登录用户的访问凭证 </a>
+ * </p>
+ */
+
+@Slf4j
+public class DingUtil {
+
+    public static void main(String[] args) {
+        GetUserTokenResponseBody userToken = DingUtil.getUserToken("8f9511a2dbf439328ee0331a3fee125c123");
+        String accessToken = userToken.getAccessToken();
+    }
+
+    private static final String SUITE_KEY = "suite8j0xog63udtsaq7g";
+    private static final String SUITE_SECRET = "NdbWhD6Iu9n5h-3IKXeepmPwmcFbOw9Em7UeiJKyq3_Wa8LgmJ-G6b8SmsOJMrMc";
+
+    /**
+     * 获取用户token
+     *
+     * <p>
+     * https://open.dingtalk.com/document/isvapp/obtain-user-token
+     */
+    public static GetUserTokenResponseBody getUserToken(String code) {
+
+        GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
+                .setClientId(SUITE_KEY)
+                .setClientSecret(SUITE_SECRET)
+                .setCode(code)
+                .setGrantType("authorization_code");
+
+        try {
+            com.aliyun.dingtalkoauth2_1_0.Client client = getClient2();
+            return client.getUserToken(getUserTokenRequest).getBody();
+        } catch (Exception e) {
+
+            TeaException teaException;
+
+            if (e instanceof TeaException) {
+                teaException = (TeaException) e;
+            } else {
+                teaException = new TeaException(e.getMessage(), e);
+            }
+
+            String errCode = teaException.getCode();
+            String errMsg = teaException.getMessage();
+
+            if (StrUtil.isAllNotBlank(errCode, errMsg)) {
+                log.error("钉钉授权认证失败: code:{},message:{}", errCode, errMsg);
+                throw new ServiceException(errMsg);
+            } else {
+                log.error("钉钉授权认证失败", teaException);
+                throw new ServiceException("发生未知异常,钉钉授权认证失败");
+            }
+        }
+    }
+
+    /**
+     * 获取用户信息
+     *
+     * <p>
+     * https://open.dingtalk.com/document/isvapp/obtain-the-userid-of-a-user-by-using-the-log-free
+     */
+    public static OapiV2UserGetuserinfoResponse.UserGetByCodeResponse getUserInfo(String code, String accessToken) {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
+        OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest();
+        req.setCode(code);
+
+        try {
+            OapiV2UserGetuserinfoResponse rsp = client.execute(req, accessToken);
+            return rsp.getResult();
+        } catch (ApiException e) {
+            String message = "钉钉获取用户信息失败: code:" + e.getErrCode() + ",message:" + e.getErrMsg();
+            log.error(message);
+            throw new ServiceException(message);
+        }
+    }
+
+    /**
+     * 获取用户个人信息
+     *
+     * <p>
+     * https://open.dingtalk.com/document/isvapp/tutorial-enabling-login-to-third-party-websites
+     */
+    public GetUserResponseBody getUserinfo(String accessToken) throws Exception {
+        com.aliyun.dingtalkcontact_1_0.Client client = getClient1();
+        GetUserHeaders getUserHeaders = new GetUserHeaders();
+        getUserHeaders.setXAcsDingtalkAccessToken(accessToken);
+
+        // 获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me
+        return client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()).getBody();
+    }
+
+    private static com.aliyun.dingtalkcontact_1_0.Client getClient1() throws Exception {
+        Config config = new Config();
+        config.protocol = "https";
+        config.regionId = "central";
+        return new com.aliyun.dingtalkcontact_1_0.Client(config);
+    }
+
+    public static com.aliyun.dingtalkoauth2_1_0.Client getClient2() throws Exception {
+        Config config = new Config();
+        config.protocol = "https";
+        config.regionId = "central";
+        return new com.aliyun.dingtalkoauth2_1_0.Client(config);
+    }
+
+}

+ 7 - 0
pom.xml

@@ -28,6 +28,7 @@
         <module>hx-account</module>
         <module>hx-sale</module>
         <module>hx-victoriatourist</module>
+        <module>hx-dingding</module>
     </modules>
 
     <properties>
@@ -142,6 +143,12 @@
                 <version>${hx.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.fjhx</groupId>
+                <artifactId>hx-dingding</artifactId>
+                <version>${hx.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>