• 钉钉开发第三方H5微应用入门详细教程[ISV][免登流程][授权码][HTTP回调推送][识别用户身份][获取用户信息]


    此教程注意点:

    • 适用于第三方企业开发 H5微应用 形式,非企业内部开发, 非钉钉推荐的“小程序”方式;
    • 消息推送模式为 HTTP回调 ,不使用钉钉收费的“RDS钉钉云推送数据源“模式;

      


    开发前准备:

    • 关于服务器,有公网服务器最好,没有的话需要 内网穿透工具;
    • 调试的时候,由于钉钉的H5微应用调试只能“真机”调试,极其恶心,所以极其建议调试的时候使用 内网穿透工具
    • 关于域名什么的,有没有无所谓,随缘;

    其他一些需要明白的:

    • 需要自备一个钉钉企业(没有的可以自己创建一个),测试应用无所谓认证不认证,发布的时候相关限制请参阅说明文档;
    • H5微应用前端网页获取当前使用的企业的corpId ,需要在 首页URL地址里面 使用 $CORPID$ 占位符 ,然后页面里解析 url 参数,可获得 corpId
    • 首页地址后面可以更改,创建时无所谓,回调地址需要搭建好我们自己的服务器,然后填写的时候需要验证有效性,可参考 服务端-示例 里面的  cn.lnexin.dingtalk.controller.SuiteCallbackController::callback(args...)
    • 在我们自身的服务器回调接口搭建好之前, 不能够填写回调地址;

    • 在配置好回调地址前, 不能进行企业授权;

    • 在回调里面激活了当前企业, 才算授权成功;
    • 在未授权之前, 手机端,PC端 肯定实在应用里面看不到我们的应用的;

    另外本教程重在说明钉钉微应用的免登流程,所以前端部分使用原生的, 最简单的 js, 仅供参考;


     目录

      一、创建H5微应用

      二、搭建微应用服务端 (服务点git示例代码地址: https://gitee.com/lne/ding-server )

      三、确认自己的服务端程序运行成功, 并且填写回调地址;

      四、实现授权 > 激活流程,将微应用添加到企业客户端的应用列表中;

      五、编写简单的微应用首页 (html网页) 进行测试;

      六、从安卓端和PC段访问,确认登录流程没有问题;


    一. 创建H5微应用

        创建完成之后:

        在客户端和PC端是看不到这个程序的, 如果想看到这个程序, 就需要 授权> 激活的流程; 而授权>激活 是依赖于我们的服务器的;

        添加有效的回调地址是为了让钉钉可以给我们发消息;

        而在我们服务器的回调地址程序里面做正确业务的处理, 才能完成授权的流程;  只有当授权完成>激活企业应用了之后, 在客户端 才能看到微应用;     

        没有有效的回调地址,不在自己服务器里面处理授权>激活流程, 那么你在客户端永远也看不到这个程序;

         

      第一步:填写基础信息

        

         第二步. 配置开发信息,配置完点击创建应用即可。

         

         配置完成之后,信息如下:

        

      在开发者后台添加完大概就这样了, 其他信息:如 回调URL(在服务端搭好之后填写), 首页地址等, 后续可以修改.

    二. 搭建微应用服务端

      服务端程序可参照 (服务端-示例

    复制代码
    1. 相关配置参数可参照上面 应用基础信息 那张图来一 一对应 .
    2. 所有的关键信息 是存储在服务端的, 如我们的suiteKey/suiteSecret/suiteTicket/aesKey/token;
    3. 所以和钉钉相关的数据交互都是在服务端,后台完成的, 除了获取 免登授权码;
    4. 我们的前端和我们的服务端交互过程中, corpId 由前端获取, 传递给我们;
    5. 服务端和钉钉交互所使用的accessToken , 可以每次都去钉钉重新获取, 但是更建议在有效期内, 后端获取一次, 然后存储在前端, 每次的数据交互将token  传递给后端;
    6. 钉钉向我们服务器发送请求, 也就是钉钉应用里面的回调地址;
    7. 钉钉的所有消息都是通过回调通知我们的, 而且消息的结构是一致的;
    复制代码

      下面这里给出一些关键代码: (完整的项目代码可参照上面的示例地址)

      1. 钉钉回调请求接收

    复制代码
    
    
    package cn.lnexin.dingtalk.controller;

    import cn.lnexin.dingtalk.service.IDingAuthService;
    import cn.lnexin.dingtalk.service.ISuiteCallbackService;
    import cn.lnexin.dingtalk.utils.JsonTool;
    import cn.lnexin.dingtalk.utils.Strings;
    import com.fasterxml.jackson.databind.JsonNode;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    import java.util.LinkedHashMap;
    import java.util.Map;
    import static cn.lnexin.dingtalk.constant.CallbackConstant.*;
     
    /**
    * [钉钉] - 钉钉的回调接口, 包含开通,授权,启用,停用,下单等
    *
    * @author lnexin@foxmail.com
    **/
    
    
    public class SuiteCallbackController {
        static Logger logger = LoggerFactory.getLogger(SuiteCallbackController.class);
    
        /**
         * 钉钉发过来的数据格式:
         * <p>
         * http://您服务端部署的IP:您的端口/callback?signature=111108bb8e6dbce3c9671d6fdb69d15066227608&timestamp=1783610513&nonce=380320111
         * 包含的json数据为:
         * {
         * "encrypt":"1ojQf0NSvw2WPvW7LijxS8UvISr8pdDP+rXpPbcLGOmIBNbWetRg7IP0vdhVgkVwSoZBJeQwY2zhROsJq/HJ+q6tp1qhl9L1+ccC9ZjKs1wV5bmA9NoAWQiZ+7MpzQVq+j74rJQljdVyBdI/dGOvsnBSCxCVW0ISWX0vn9lYTuuHSoaxwCGylH9xRhYHL9bRDskBc7bO0FseHQQasdfghjkl"
         * }
         */
    
        @Autowired
        ISuiteCallbackService suiteCallbackService;
    
        /**
         * 钉钉服务器推送消息 的地址
         *
         * @param signature
         * @param timestamp
         * @param nonce
         * @param encryptNode
         * @return
         */
        @PostMapping(value = "/callback")
        public Map<String, String> tempAuthCodeCallback(@RequestParam String signature,
                                                        @RequestParam String timestamp,
                                                        @RequestParam String nonce,
                                                        @RequestBody JsonNode encryptNode) {
            String encryptMsg = encryptNode.get("encrypt").textValue();
            String plainText = suiteCallbackService.decryptText(signature, timestamp, nonce, encryptMsg);
            JsonNode plainNode = JsonTool.getNode(plainText);
    
            //进入回调事件分支选择
            Map<String, String> resultMap = caseProcess(plainNode);
            return resultMap;
        }
    
        /**
         * 根据回调数据类型做不同的业务处理
         *
         * @param plainNode
         * @return
         */
        private Map<String, String> caseProcess(JsonNode plainNode) {
            Map<String, String> resultMap = new LinkedHashMap<>();
            String eventType = plainNode.get("EventType").textValue();
            switch (eventType) {
                case SUITE_TICKET_CALLBACK_URL_VALIDATE:
                    logger.info("[callback] 验证回调地址有效性质:{}", plainNode);
                    resultMap = suiteCallbackService.encryptText(CALLBACK_RETURN_SUCCESS);
                    break;
                case TEMP_AUTH_CODE_ACTIVE:
                    logger.info("[callback] 企业开通授权:{}", plainNode);
                    Boolean active = suiteActive(plainNode);
                    resultMap = suiteCallbackService.encryptText(active ? CALLBACK_RETURN_SUCCESS : ACTIVE_RETURN_FAILURE);
                    break;
                case SUITE_RELIEVE:
                    logger.info("[callback] 企业解除授权:{}", plainNode);
              // 处理解除授权逻辑break;
                case CHECK_UPDATE_SUITE_URL:
                    logger.info("[callback] 在开发者后台修改回调地址:" + plainNode);
                    resultMap = suiteCallbackService.encryptText(CALLBACK_RETURN_SUCCESS);
                    break;
                case CHECK_CREATE_SUITE_URL:
                    logger.info("[callback] 检查钉钉向回调URL POST数据解密后是否成功:" + plainNode);
                    resultMap = suiteCallbackService.encryptText(CALLBACK_RETURN_SUCCESS);
                    break;
                case CONTACT_CHANGE_AUTH:
                    logger.info("[callback] 通讯录授权范围变更事件:" + plainNode);
                    break;
                case ORG_MICRO_APP_STOP:
                    logger.info("[callback] 停用应用:" + plainNode);
                    break;
                case ORG_MICRO_APP_RESTORE:
                    logger.info("[callback] 启用应用:" + plainNode);
                    break;
                case MARKET_BUY:
                    logger.info("[callback] 用户下单购买事件:" + plainNode);
                    // 处理其他企业下单购买我们应用的具体逻辑
                    break;
                default:
                    logger.info("[callback] 未知事件: {} , 内容: {}", eventType, plainNode);
                    resultMap = suiteCallbackService.encryptText("事件类型未定义, 请联系应用提供方!" + eventType);
                    break;
            }
            return resultMap;
        }
    
        /**
         * 激活应用授权
         * tmp_auth_code
         */
        private Boolean suiteActive(JsonNode activeNode) {
            Boolean isActive = false;
            String corpId = activeNode.get("AuthCorpId").textValue();
            String tempAuthCode = activeNode.get("AuthCode").textValue();
    
            String suiteToken = suiteCallbackService.getSuiteToken();
            String permanentCode = suiteCallbackService.getPermanentCode(suiteToken, tempAuthCode);
            if (!Strings.isNullOrEmpty(permanentCode)) {
                isActive = suiteCallbackService.activateSuite(suiteToken, corpId, permanentCode);
            } else {
                logger.error("获取永久授权码出错");
            }
            return isActive;
        }
    复制代码

     工具实现: 

     SuiteCallbackServiceImpl.java

    构建发布程序, 发布到自己的服务器上. 如果使用内网穿透工具, 请忽略;

     三. 确认自己的服务端程序运行成功, 并且填写回调地址

      根据上面的相关说明将服务端放置在自己的公网服务器也好,或者使用相关的 内网穿透工具 也好  (自行解决)

      总之, 现在要有一个可以访问我们 服务端项目的 公网地址 

      确保你自己的服务器可以使用公网地址访问到,并且成功返回数据;

      同时确保:

    1. 必须有回调地址借口用来接收钉钉发送的消息;                                    (本文示例地址:  /ding/callback )
    2. 必须有一个接收免登授权码和企业corpId 来返回用户信息的接口;      (本文示例地址:  /ding/login )

      比如我自己的测试例子为: 

    复制代码
    // 这里是我自己的测试地址 http://你的公网地址/ding/config
    {
            "suiteId": "6707015",
            "suiteKey": "suiteqflsxxxxxxxx",
            "suiteSecret": "E7TH7H3hGtmhtoGDgq8adJhn0xxxxxxxxxxxBf-GQSTWl8NTs6_",
            "suiteToken": "customtoken",
            "encodingAESKey": "qwp51j1k8eiudktvnip2dwrkqxxxxxcci",
            "suiteTicket": "customTestTicket",
            "url_suite_token": "https://oapi.dingtalk.com/service/get_suite_token",
            "url_permanent_code": "https://oapi.dingtalk.com/service/get_permanent_code",
            "url_activate_suite": "https://oapi.dingtalk.com/service/activate_suite",
            "url_get_auth_info": "https://oapi.dingtalk.com/service/get_auth_info",
            "url_get_access_token": "https://oapi.dingtalk.com/service/get_corp_token",
            "url_get_user_id": "https://oapi.dingtalk.com/user/getuserinfo",
            "url_get_user_item": "https://oapi.dingtalk.com/user/get"
      
    }
    复制代码

      

     四. 实现授权 > 激活流程,将微应用添加到企业客户端的应用列表中

      现在,经过以上步骤, 我们已经准备好的东西有:

    1. 公网可以访问的服务端地址, 接收钉钉发给我们的消息(回调地址)如: http://ding.lnexin.cn/server/ding/callback,我们自己的登录地址,如: http://ding.lnexin.cn/server/ding/login
    2. 在钉钉开发者平台创建配置好的一个H5微应用;
    3. 确保服务端的参数和微应用的基础信息一致;

              

      完成上述步骤,在客户端依旧是没有应用入口的,如:

          

      

      下面需要在开发者平台进行授权

       

      点击授权之后,会在我们服务器收到钉钉发给我们的消息,我们服务端在经过一系列处理之后,向钉钉发送激活企业的请求,如果激活成功,那么授权就成功了;

      点击授权后服务器收到的消息:   

      

       如果激活成功,如下所示:

      

      此时授权激活成功,在客户端就有了相关微应用入口。如:

       

       至此,所有前置准备工作已经完成,下面主要是免登和页面jsapi 对接。

    五. 编写简单的微应用首页 (html网页) 进行测试

       经过前面的步骤,我们现在可以看到微应用,并且拥有了可访问的公网服务端接口地址。

      现在需要准备一个前端的公网地址,如果是使用springboot 前后端一体的可以忽略。( 我这里是分离的,大家需要根据自己的情况而定,示例地址如:  http://ding.lnexin.cn/ )

      下面我们编写一个最简单前端html 网页:

      

       

       html 前端示例代码如下:(git仓库

     index.html

    六. 从安卓端和PC段访问, 确认流程没有问题;

      差不多第三方企业开发的免登和授权流程已经完毕了,剩下的就是每个应用自己的业务逻辑处理了,这个个人自己解决吧。

  • 相关阅读:
    适用于Bash编程初学者小例子
    Linux下的压缩与解压命令速查
    Linux下拷贝一个带有soft link的dir,会把被link的内容也拷贝过来吗?
    适用于Bash编程初学者小例子
    从一个git仓库迁移代码到另一个git仓库(亲测有效版)(转)
    海量数据面试题(附题解+方法总结)(转)
    leetcode刷题(六)路径总和I、II、III(转)
    浅谈AVL树,红黑树,B树,B+树原理及应用(转)
    [LeetCode] 860. Lemonade Change 买柠檬找零(转)
    [leetcode] 134. Gas Station 解题报告(转)
  • 原文地址:https://www.cnblogs.com/ZenoLiang/p/12677392.html
Copyright © 2020-2023  润新知