我们本来是有个app,已经正常上线使用了,框架用的是uniapp。现在想搞到钉钉应用上,一开始想做钉钉小程序,就先简单说一下钉钉小程序基础开发,然后再详细介绍钉钉网页应用。

钉钉小程序基础开发 

首先说一下不管是开发钉钉小程序还是网页应用(微应用)都需要在钉钉后台新建应用,因为会用到一些id、Secret之类的。 钉钉新建应用

然后就是开发工具的下载: 小程序开发工具 。其实直接用支付宝开发工具即可,这也是官网推荐的: 小程序开发工具

然后我们使用HBuilderX打开项目,开发钉钉小程序需要在package.json中添加如下代码:

"uni-app": {
	"scripts": {
		"mp-dingtalk": {
			"title": "钉钉小程序",
			"env": {
				"UNI_PLATFORM": "mp-alipay"
			},
			"define": {
				"MP-DINGTALK": true
			}
		}
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

然后我在manifest.json中也加入了:

"mp-dingtalk": {
	"title": "钉钉小程序",
	"env": {
		"UNI_PLATFORM": "safe-in-product-app"
	},
	"define": {
		"MP-DINGTALK": true
	},
	"optimization": {
		"subPackages": true
	}
},
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

然后可以重启一下HBuilderX这样我们点击【运行】,就会出现【钉钉小程序】了:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_spring

然后我们可能需要在HBuilderX工具的设置中,手动设置一下对应的小程序工具的位置:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_uni-app_02

然后点击【运行】就可以调起来,然后手动选择对应的编译好的代码打开即可 ,这个时候我们注意要选择钉钉应用:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_spring_03

然后运行即可,发布的话直接在右上角点击【上传版本】即可 。

然后小程序搞了半天,由于包过大,发现上传不上去,预览也预览不了,而且运行编译的过程是很漫长的,超级慢,体验特别差。大家也都知道,不管是微信小程序、支付宝小程序还是钉钉小程序或者其他的小程序,只要是小程序,要求的屁事就很多,限制这限制那的,所以考虑了一下决定放弃小程序!!!于是开始了钉钉网页应用的研究,钉钉网页应用就是H5,其实跟微信公众号差不多的意思。

钉钉网页应用

同样的我们现在钉钉后台先建网页应用:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_JSON_04

因为开发钉钉应用肯定要在钉钉环境中才行,那么怎么调试呢?直接运行到浏览器是肯定不行的。官网也给我们提供了几种调试方式:

 微应用调试工具

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_JSON_05

我比较喜欢的就是【微应用调试工具-RC版】,点击PC端下载即可,其实也是个钉钉,我们正常登录即可,不一样的是,我们点开应用,可以像浏览器一样【F12】打开控制台!!!当然前提是我们要把刚才建的应用先发布:  版本详情 ,如图:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_uni-app_06

注意在发布之前,我们要设置一下网页链接为我们本地开发的地址:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_JSON_07

保存好之后,再发布哈!

这样我们在钉钉上就能看到我们的应用了,然后我们把前端本地项目启动起来就可以了。

那么接下来就是功能的开发,功能的开发这里分前端和后端,先说后端,再说前端哈,把后端的接口都写好,前端调用就行了。后端用的就是我们最普遍的springboot哈

后端实现:

因为我这里开发的钉钉应用是企业内部应用,所以用到的一些id、密钥之类的东西,我先都写在配置文件中哈:

application.yml

config:
  dingh5:
    corpid: dingbe8dxxxxxx
    apiToken: 6677f5xxxxxxxx
    appid: 6edaxxxxxxxx
    agentId: 3xxxxxx
    clientId: dingbuxxxxxxx
    clientSecret: 1qxxxxxx
    accessTokenUrl: https://api.dingtalk.com/v1.0/oauth2/{corpId}/token
    userInfoUrl: https://oapi.dingtalk.com/topapi/v2/user/getuserinfo
    userDetailUrl: https://oapi.dingtalk.com/topapi/v2/user/get
    jsapiTicketUrl: https://api.dingtalk.com/v1.0/oauth2/jsapiTickets
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

上面的id之类的东西在钉钉后台上都是可以找到的哈,有企业的、有应用的,大家自行替换一下。

然后是映射类,将配置文件的变量映射到类中,方便使用

DingH5Config.java

package com.wft.wftappgen.model;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "config.dingh5")
public class DingH5Config {

    private String corpid;

    private String apiToken;

    private String appid;

    private String agentId;

    private String clientId;

    private String clientSecret;

    private String accessTokenUrl;

    private String userInfoUrl;

    private String userDetailUrl;

    private String jsapiTicketUrl;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

DingH5Controller.java 

package com.wft.wftappgen.controller;

import com.wft.wftappgen.model.ActionResult;
import com.wft.wftappgen.service.DingH5Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/Ding")
public class DingH5Controller {

    @Autowired
    private DingH5Service dingH5Service;

    /**
     * 获取钉钉客户端 JSAPI 鉴权配置信息
     * @param url 当前网页的URL,不包含#及其后面部分。
     * @return
     */
    @PostMapping("/config")
    public ActionResult getDingConfigSign(@RequestBody Map<String, Object> map) throws Exception {
        return dingH5Service.getDingConfigSign((String) map.get("url"));
    }

    /**
     * 获取企业内部应用/企业三方应用 accessToken
     * 这个前端可以不调用,后端直接内部调用即可
     * @return
     */
    @GetMapping("/token")
    public ActionResult getAccessToken() {
        return dingH5Service.getAccessToken();
    }

    /**
     * 获取用户信息
     * @param code 网页应用免登授权码
     * @return
     */
    @GetMapping("/userInfo/{code}")
    public ActionResult getUserInfo(@PathVariable("code") String code) {
        return dingH5Service.getUserInfo(code);
    }

    /**
     * 获取用户详情
     * @param userid 用户id
     * @return
     */
    @GetMapping("/userDetail/{userid}")
    public ActionResult getUserDetail(@PathVariable("userid") String userid) {
        return dingH5Service.getUserDetail(userid);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.

service接口就不写了哈,直接是实现类

DingH5ServiceImpl.java 

package com.wft.wftappgen.service.impl;

import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.wft.wftappgen.model.ActionResult;
import com.wft.wftappgen.model.DingH5.DingH5TokenVO;
import com.wft.wftappgen.model.DingH5Config;
import com.wft.wftappgen.service.DingH5Service;
import com.wft.wftappgen.utils.DdConfigSign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class DingH5ServiceImpl implements DingH5Service {

    @Autowired
    private DingH5Config dingH5Config;

    /**
     * https://open.dingtalk.com/document/orgapp/jsapi-request-auth-code
     * 不论你的应用是企业内部应用还是企业三方应用,都推荐你使用此文档获取应用的 Access Token
     * @return
     */
    @Override
    public ActionResult getAccessToken() {
        String corpid = dingH5Config.getCorpid();
        String accessTokenUrl = dingH5Config.getAccessTokenUrl().replace("{corpId}", corpid);
        Map<String, String> params = new HashMap<>();
        params.put("client_id", dingH5Config.getClientId());
        params.put("client_secret", dingH5Config.getClientSecret());
        params.put("grant_type", "client_credentials");
        String body = HttpRequest.post(accessTokenUrl)
                .header("Content-Type", "application/json")
                .body(JSON.toJSONString(params))
                .execute()
                .body();
        DingH5TokenVO dingH5TokenVO = JSON.parseObject(body, DingH5TokenVO.class);
        return ActionResult.success(dingH5TokenVO);
    }

    @Override
    public ActionResult getDingConfigSign(String url) throws Exception {
        DingH5TokenVO tokenVO = (DingH5TokenVO) getAccessToken().getData();
        String accessToken = tokenVO.getAccess_token();
        String jsapiTicketUrl = dingH5Config.getJsapiTicketUrl();
        String body = HttpRequest.post(jsapiTicketUrl)
                .header("Content-Type", "application/json")
                .header("x-acs-dingtalk-access-token", accessToken)
                .execute()
                .body();
        Map<String, Object> map = JSON.parseObject(body, Map.class);
        String jsapiTicket = (String) map.get("jsapiTicket");
        String nonceStr = DdConfigSign.getRandomStr(6);
        long timeStamp = new Date().getTime();
        String signature = DdConfigSign.sign(jsapiTicket, nonceStr, timeStamp, url);
        Map<String, Object> config = new HashMap<>();
        config.put("agentId", dingH5Config.getAgentId());
        config.put("corpId", dingH5Config.getCorpid());
        config.put("timeStamp", timeStamp);
        config.put("nonceStr", nonceStr);
        config.put("signature", signature);
        return ActionResult.success(config);
    }

    @Override
    public ActionResult getUserInfo(String code) {
        DingH5TokenVO tokenVO = (DingH5TokenVO) getAccessToken().getData();
        String accessToken = tokenVO.getAccess_token();
        String userInfoUrl = dingH5Config.getUserInfoUrl();
        Map<String, String> map = new HashMap<>();
        map.put("code", code);
        String body = HttpRequest.post(userInfoUrl + "?access_token=" + accessToken)
                .header("Content-Type", "application/json")
                .body(JSON.toJSONString(map))
                .execute()
                .body();
        Map<String, Object> userInfo = JSON.parseObject(body, Map.class);
        return ActionResult.success(userInfo);
    }

    @Override
    public ActionResult getUserDetail(String userid) {
        String accessToken = ((DingH5TokenVO) getAccessToken().getData()).getAccess_token();
        Map<String, Object> map = new HashMap<>();
        map.put("userid", userid);
        map.put("language", "zh_CN");
        String userDetailUrl = dingH5Config.getUserDetailUrl();
        String body = HttpRequest.post(userDetailUrl + "?access_token=" + accessToken)
                .header("Content-Type", "application/json")
                .body(JSON.toJSONString(map))
                .execute()
                .body();
        Map<String, Object> userDetail = JSON.parseObject(body, Map.class);
        return ActionResult.success(userDetail);
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.

用到了个DingH5TokenVO和DdConfigSign签名工具类,也贴在下方:

DingH5TokenVO.java

这个其实不用也行,看自己吧

package com.wft.wftappgen.model.DingH5;

import lombok.Data;

@Data
public class DingH5TokenVO {
    private String access_token;

    private Integer expires_in;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

 DdConfigSign.java

package com.wft.wftappgen.utils;

import java.net.URL;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.Random;

/**
 * 计算dd.config的签名参数 signature
 **/
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 + "×tamp=" + 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();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.

到此,后端接口就写好了,只写了调用钉钉相关的接口哈,没有与业务相关的代码,大家可以自行集成到业务中,比如要登录的话,我们获取到钉钉用户信息、用户详情后,可以跟目前系统的用户进行匹配,返回对应的token和用户信息。

这里需要注意一点:就是获取用户详情这个接口调用,是需要权限的,否则会报错如下:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_钉钉_08

添加权限可以看下官网这篇: 添加接口调用权限 

前端实现:

前端的话,我们要实现免登功能,就是在uniapp中pages.json中的第一个“空”页面

或者叫做“过渡”页面,就是项目启动,先在这个页面中进行判断,判断当前的环境是否是钉钉环境,如果是钉钉环境,我们就正常走自动登录的逻辑,那如果不是的话,就还是我们之前的逻辑,进入登录页面

首先安装: 

pnpm add dingtalk-jsapi
  • 1.

然后是我们的调用的后端接口(ddLogin/index.js)

import { get, post } from '@/framework/utils/request/index.js';
import { getApiPrefix } from '@/framework/utils/request/index.js';

// 获取TOKEN(这个不需要哈,这个token是获取的钉钉的access_token,正常应该是咱们业务的token,跟后端一块儿商量好)
export const getDDAccessToken = () => {
  return get({
    url: `${getApiPrefix()}/Ding/token`
  });
};

// 获取钉钉客户端 JSAPI 鉴权配置信息
export const getJsapiConfig = (url = window.location.href.split('#')[0]) => {
  return post({
    url: `${getApiPrefix()}/Ding/config`,
    data: { url }
  });
};

// 获取用户信息
export function getDDUserInfo(code) {
	return get({
		url: `${getApiPrefix()}/Ding/userInfo/${code}`,
	})
}

// 获取用户详情
export function getDDUserDetail(userid) {
	return get({
		url: `${getApiPrefix()}/Ding/userDetail/${userid}`,
	})
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

pages.json中pages的第一个过渡页面(welcom.vue) 

<script setup>
import { getToken } from "@/framework/utils/auth/index.js";
import { getUserInfoStorage } from "@/framework/utils/auth/index.js";
import { getUserinfo } from "@/framework/api/mine/mine.js";
import { onShow } from "@dcloudio/uni-app";
import { isDDEnv, autoLogin } from '@/utils/ddUtils'

onShow(async () => {
  uni.getNetworkType().then(async res => {
    if (res.networkType == "none") {
      if (isDDEnv()) {
        autoLogin()
      } else {
        uni.reLaunch({
          url: "/pages/login/login?type=noline"
        });
      }
      return;
    }
    if (getToken() && getUserInfoStorage()) {
      uni.showLoading()
      // 检查用户信息是否完整
      const res = await getUserinfo();
      uni.hideLoading();
      if (res.code === 200) {
        uni.reLaunch({
          url: "/pages/welcome/index"
        });
      } else {
        if (isDDEnv()) {
          autoLogin()
        } else {
          uni.reLaunch({
            url: "/pages/login/login"
          });
        }
      }
    } else {
      if (isDDEnv()) {
        autoLogin()
      } else {
        uni.reLaunch({
          url: "/pages/login/login"
        });
      }
    }
  });
});
</script>
<template></template>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

ddUtils.js 

import * as dd from 'dingtalk-jsapi';
import { getJsapiConfig, getDDUserInfo, getDDUserDetail } from '@/api/business/ddLogin'

export async function autoLogin() {
  // 1. 钉钉jsapi鉴权(如果只是做免登调用requestAuthCode其实是不需要鉴权即可调用,但是为了防止后面还会用到钉钉的其他api,所以这里统一做了jsapi鉴权)
  await dingJsapiAuth()
	// 2. 获取免登授权码
  const CORPID = import.meta.env.VITE_APP_DD_CORPID
  const CLIENTID = import.meta.env.VITE_APP_DD_CLIENTID
	const code = await getDDAuthCode(CORPID, CLIENTID)
  console.log(code, 'code-------->>')
  // 3. 获取用信息(token)
  const userInfo = await getDDUserInfo(code).then(res => res.data)
  console.log(userInfo, 'userInfo-------->>')
	// 4. 获取用户详情
	const userDetail = await getDDUserDetail(userInfo.result.userid).then(res => res.data)
  console.log(userDetail, 'userDetail-------->>')
  // 5. 本地存储 token、userinfo等信息
  // 存储部分code...
  // 6. 跳转首页
  uni.reLaunch({
	  url: "/pages/welcome/index"
	})
}

// 钉钉jsapi鉴权
export function dingJsapiAuth() {
  return new Promise(async (resolve, reject) => {
    let data;
    try {
      data = await getJsapiConfig().then(res => res.data)
    } catch(err) {
      reject(err)
    }
    if(!data) reject()
    const config = {
      ...data,
      type: 0, // 选填。0表示微应用的jsapi,1表示服务窗的jsapi;不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持
      jsApiList: ['biz.contact.choose'] // 必填,需要使用的jsapi列表,注意:不要带dd。
    }
    dd.config(config)
    resolve()
  })
}

/**
 * 获取网页应用免登授权码
 * @param {*} corpId 
 * @param {*} clientId 
 * @returns 
 */
function getDDAuthCode(corpId, clientId) {
	return new Promise((resolve, reject) => {
		dd.ready(() => {
      // 用旧方法没问题
			dd.runtime.permission.requestAuthCode({
			  corpId: corpId,
			  onSuccess: result => {
			    const { code } = result
			    resolve(code)
			  },
			  onFail: err => reject(err)
			})
      // 用requestAuthCode新方法会报错:不存在的临时授权码(目前我测试了很多次,都是这个错误 )
			// dd.requestAuthCode({
			//   corpId: corpId,
			//   clientId: clientId,
			//   onSuccess: result => {
			//     const { code } = result
			//     resolve(code)
			//   },
			//   onFail: err => reject(err)
			// })
		})
	})
}

// 判断H5中是否在钉钉环境
// dd.env.platform != "notInDingTalk" 不靠谱 运行到浏览器是notInDingTalk、手机是android或ios、从电脑钉钉上打开是pc、从手机上打开是ios、android
export function isDDEnv() {
	// #ifdef H5
	const ua = navigator.userAgent.toLowerCase();
	return ua.indexOf('dingtalk') !== -1;
	// #endif
	// #ifndef H5
	return false
	// #endif
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.

这里注意:上面获取网页应用免登授权码的时候,我一开始是严格按照官网上的来写的,也就是用的dd.requestAuthCode  方法来获取的,确实是获取到了,然后获取用户信息的时候总是报错如下:

uniapp实现钉钉网页应用jsapi鉴权、免登功能(前后端)_uni-app_09

就不管是在前端调用,还是后端调用,还是postman调用都是这个错误,当时都把网上的类似的错误的问题都翻烂了,网上说的没一个有用的。最后才试了一下旧版的api,也就是dd.runtime.permission.requestAuthCode 这样调用,这样调用我看还少个clientId参数,结果就可以了。看来还是旧版的api好用,这个我是真没想到,我开始以为旧版的api在将来会不会有可能废弃掉,所以一开始就没想考虑旧版的api。

然后还有一点需要注意的:就是如果我们没有进行鉴权(就是dd.config),那我们就不需要使用dd.ready()方法,直接调用对应的方法即可。不鉴权只能调用不需要鉴权的api,钉钉还是有很多api 都是需要鉴权的,虽然我这里实现网页应用免登没用到需要鉴权才能调用的api,但是我也把鉴权给做了,防止后面用到,用到的时候,直接看我这篇就可以了。

这就是钉钉网页应用的鉴权、免登相关的实现了。

之前也写过微信网页应用(微信公众号)相关的微信授权登录,跟钉钉其实也差不多,不过微信公众号第一步的code需要引导用户进行授权来获取,然后微信的大部分api也都是需要鉴权才可以使用。大家感兴趣可以去看看我之前的博客:uniapp实现微信公众号网页(H5)授权登陆获取用户信息(前端+后端)

好了,这篇到这儿就结束了~~~