Java 实现钉钉企业内部应用免登

首先要通过钉钉开放平台创建H5微应用,得到企业id(CorpId),和H5微应用凭证:微应用的ID(AgentId)、微应用的唯一标识key(AppKey)、密钥(AppSecret)。

准备工作

第一、你需要有一个 自己的 企业钉钉,进入企业钉钉管理,如下图所示

第二、看创建H5微应用的教程,链接地址:开发一个钉钉H5微应用 - 钉钉开放平台 ,这个教程上面有很详细的创建微应用的步骤以及注意事项。

 查看微应用配置信息


以下的AgentId、AppKey、AppSecret会用到,所以找个地方保存一下,如果你是管理员那就更好了,随时都可以登录钉钉组织查看哦。

基础信息


开发管理


重点来喽:服务器出口IP,自己刚开始配置的时候不清楚到底是什么意思,也不懂服务器出口IP是干什么用的,虽然官网教程上写的有哈。其实呢,这个服务器出口IP(1、本地测试时:填写你 电脑的IP + 端口 + 映射路径;2、测试环境或者生产环境时:你的测试或者生产 服务器IP + 端口 + 映射路径或者是域名 + 端口号 + 映射路径)就是应用所在的电脑IP,可以这样简单理解哈。

 

 权限管理


注意:如果想要获取钉钉用户的手机号,需要开通 企业员工手机号信息 这一个权限哦。

 一般只需要开启以下几个权限即可,其他权限可根据需要自行开通哦。

版本管理与发布


只有点击确认发布后,才可以在钉钉工作台看到微应用。

钉钉免登录流程:
  1、前端通过CorpId获取免登授权码code;
  2、后端通过AppKey和AppSecret获取access_token;
  3、使用免登授权码code和access_token去获取用户userid;
  4、通过access_token和userid去获取用户详情userinfo;
  5、拿userinfo信息去和登录用户去比较,如果一致,则免登录成功。

注意事项
因为是企业内部应用免登录,所以不需要鉴权。免登授权码code每次请求返回的数据都不一样,请求一次的时效是5分钟,access_token有效期7200秒,自动续期。

前端代码

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<script src="/webjars/jquery/jquery.min.js"></script>
	<meta name="viewport" content="width=device-width,initial-scale=1 user-scalable=0" />
	<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
	<!-- 引入JSAPI -->
	<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/2.10.3/dingtalk.open.js"></script>
	<title>企业内部应用钉钉免登录</title>
</head>
<script type="text/javascript">

	function relevance(){
		// 此方法必须在钉钉环境下才能执行
		dd.ready(function() {
			// 获取免登授权码code
			dd.runtime.permission.requestAuthCode({
				corpId : "dingxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" , // 企业id
				onSuccess : function(result) {
					var code = result.code;
					getUserInfo(code); //通过该code可以获取用户身份
				},
				onFail : function(err) {
					alert('获取授权码失败: ' + JSON.stringify(err));
				}
			});
		});
	}

	function getUserInfo(code) {
		$.ajax({
			type : "GET",
			url : "/ddUser/getUserInfo?code=" + code,
			contentType : "application/json;charset=utf-8",
			success : (function(res) {
				if(res.isLogin){
					alert("登陆成功!")
				}else{
					alert("登录失败!");
				}
			}),
			error: (function (err) {
				alert("调用getUserInfo失败 err为:" + JSON.stringify(err));
			}),
		});
	}
</script>
<body style="padding-top: 52px;text-align: center;">
	<button onclick="relevance()">立即绑定</button>
</body>
</html>

后端代码

将钉钉开放平台中得到的CorpId,AgentId,AppKey,AppSecret放到application.yml里面。

azure:
	corpid: dingxxxxxxxxxxxxxxxxxxxxxxxxxxx
	appkey: dingxxxxxxxxxxxxxxx
	appsecret: RVYcJvDtymXWsD9lwXBJRd1qKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
	agentid: 9718xxxxx

 Pom.xml

        <!--引入钉钉sdk-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
            <version>2.0.0</version>
        </dependency>

钉钉开发文档地址:

成为钉钉开发者 - 钉钉开放平台

创建工具类

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class HttpHelper {

	@Value("${azure.corpid}")
    private static String corpid;

    @Value("${azure.appsecret}")
    private static String appsecret;

    @Value("${azure.agentid}")
    private static String agentid;

    @Value("${azure.appkey}")
    private static String appkey;

	public static JSONObject httpGet(String url){
        //创建httpClient
        CloseableHttpClient httpClient= HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);                             //生成一个请求
        RequestConfig requestConfig = RequestConfig.custom().         //配置请求的一些属性
                setSocketTimeout(2000).setConnectTimeout(2000).build();
        httpGet.setConfig(requestConfig);                             //为请求设置属性
        CloseableHttpResponse response = null;
        try {
            response=httpClient.execute(httpGet);
            //如果返回结果的code不等于200,说明出错了
            if (response.getStatusLine().getStatusCode() != 200) {
                return null;
            }
            HttpEntity entity = response.getEntity();                 //reponse返回的数据在entity中
            if(entity!=null){
                String resultStr= EntityUtils.toString(entity,"utf-8");//将数据转化为string格式
                JSONObject result= JSONObject.parseObject(resultStr);  //将结果转化为json格式
                if(result.getInteger("errcode")==0){                  //如果返回值得errcode值为0,则成功
                    //移除一些没用的元素
                    result.remove("errcode");
                    result.remove("errmsg");
                    return result;                                    //返回有用的信息
                }else{                                                 //返回结果出错了,则打印出来
                    int errCode = result.getInteger("errcode");
                    String errMsg = result.getString("errmsg");
                    throw new Exception("ErrorCode:"+errCode+"ErrorMsg"+errMsg);
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (response != null) try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

	// 调用httpGet方法获得access_token的代码实现:
    public static String getAccess_Token(String appkey,String appsecret){
        String url="https://oapi.dingtalk.com/gettoken?"+"appkey="+appkey+"&appsecret="+appsecret;
        JSONObject res= HttpHelper.httpGet(url);                      //将httpGet方法封装在HttpHelper类中
        String access_token="";
        if(res!=null){
            access_token=res.getString("access_token");
        }else{
            new Exception("获取token异常");
        }
        return access_token;
    }

	// 执行POST请求(如果免登录成功后不需要推送钉钉消息,则不需要此方法)
	public static String httpPost(String url, Map<String, Object> params, String encoding) throws Exception {
		String result = "";
		// 创建默认的httpClient实例.
		CloseableHttpClient httpclient = HttpClients.createDefault();
		// 创建httppost
		HttpPost httppost = new HttpPost(url);
		//参数
		List<NameValuePair> formparams = new ArrayList<NameValuePair>();
		if (params != null) {
		    Set<String> keys = params.keySet();
		    for (String key : keys) {
		        formparams.add(new BasicNameValuePair(key, params.get(key).toString()));
		    }
		}
		UrlEncodedFormEntity uefEntity;
		try {
		    uefEntity = new UrlEncodedFormEntity(formparams, encoding);
		    httppost.setEntity(uefEntity);
		    CloseableHttpResponse response = httpclient.execute(httppost);
		    try {
		        Header[] headers = response.getAllHeaders();
		        for (Header header : headers) {
		            log.debug(header.getName() + "-->" + header.getValue());
		        }
		        HttpEntity entity = response.getEntity();
		        if (entity != null) {
		            result = EntityUtils.toString(entity, encoding);
		        }
		    } finally {
		        response.close();
		    }
		} catch (IOException e) {
		    throw e;
		} finally {
		    // 关闭连接,释放资源
		    try {
		        httpclient.close();
		    } catch (IOException e) {
		    }
		}
			return result;
		}
}

Controller层

import com.alibaba.fastjson.JSONObject;
import com.totyu.mmc.controller.dingding2.model.contact.User;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/ddUser")
@Api(value = "/ddUser")
public class DingdingController {

	@Value("${azure.corpid}")
    private String corpid;

    @Value("${azure.appkey}")
    private String appkey;

    @Value("${azure.appsecret}")
    private String appsecret;

    @Value("${azure.agentid}")
    private String agentid;

	private static final String GET_USERINFO_BYCODE_URL="https://oapi.dingtalk.com/user/getuserinfo?access_token=ACCESSTOKEN&code=CODE";
	
	private static final String GET_USER_URL="https://oapi.dingtalk.com/user/get?access_token=ACCESSTOKEN&userid=USERID";

	/** 根据免登授权码Code查询免登用户
     * @param code
     */
    @GetMapping("/getUserInfo")
    public JSONObject getUserInfo(@RequestParam("code") String code) {
        JSONObject jsonObject = new JSONObject();
        try {
            // 获取access_token
            String accessToken = HttpHelper.getAccess_Token(appkey,appsecret);

            // UserId的URL
            String getUserIdURL = GET_USERINFO_BYCODE_URL.replace("ACCESSTOKEN", accessToken).replace("CODE", code);
            // 发起GET请求,获取返回结果
            jsonObject = HttpHelper.httpGet(getUserIdURL);
            String userid = (String)jsonObject.get("userid");

            // User详情的URL
            String getUserURL = GET_USER_URL.replace("ACCESSTOKEN", accessToken).replace("USERID", userid);
            // 发起GET请求,获取返回结果
            jsonObject = HttpHelper.httpGet(getUserURL);

            // 钉钉发送消息给用户
            sendMessage(accessToken,userid,(String)jsonObject.get("name"));
            jsonObject.put("isLogin",true);

        } catch (Exception e) {
            e.printStackTrace();
            jsonObject.put("isLogin",false);
        }
        return jsonObject;
    }

	// 钉钉发送消息给用户
    private void sendMessage(String token, String userid, String userName) throws Exception{
        String messageUrl = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token="+token;
        Map<String, Object> map = new HashMap<>();
        map.put("agent_id", agentid);
        map.put("userid_list", userid);
        map.put("to_all_user", false);
        SimpleDateFormat dateformat = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
        String date = dateformat.format(new Date());
        String content = "用户"+userName+"在"+ date +"时成功登录xxx!";
        String msg = "{\"msgtype\":\"text\",\"text\":{\"content\":"+"\""+content+"\""+"}}";
        JSONObject jsonObj = JSONObject.parseObject(msg);
        map.put("msg",jsonObj);
        HttpHelper.httpPost(messageUrl,map,"UTF-8");
    }
}

因官方文档不建议频繁调用gettoken接口,所以进行本地缓存,代码如下;

	public static final long cacheTime = 1000 * 60 * 55 * 2;
    private static long LAST_TIME = 0;
    private static String ACCESS_TOKEN = null;


	/**
     * 获取accessToken
     * access_token的有效期为7200秒(2小时),有效期内重复获取会返回相同结果并自动续期,过期后获取会返回新的access_token。
     * 不能频繁调用gettoken接口,否则会受到频率拦截。
     * @return
     * @throws ApiException
     */
    public static String getAccessToken() {
        long curTime = System.currentTimeMillis();
        long differ = curTime - LAST_TIME;

        if (ACCESS_TOKEN != null && differ < cacheTime)
            return ACCESS_TOKEN;

        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(SysConfigCache.appId);
        request.setAppsecret(SysConfigCache.appSecret);
        request.setHttpMethod("GET");
        OapiGettokenResponse response = null;
        try {
            response = client.execute(request);
        } catch (ApiException e) {
            log.error("dingUtil getAppToken error", e);
            throw new HCException(CommonErrorCode.E100002, "获取token失败");
        }
        if (!response.isSuccess()) {
            log.error("dingUtil getAccessTokenAfresh failed, errorCode={}, errorMsg={}", response.getErrcode(), response.getErrmsg());
            return null;
        }
        ACCESS_TOKEN = response.getAccessToken();
        LAST_TIME = curTime;

        return ACCESS_TOKEN;
    }

	@ApiOperation(value = "企业内部应用免登", notes = "企业内部应用免登")
    @PostMapping(value = "/login")
    public void login (@RequestParam("requestAuthCode") String requestAuthCode) throws ApiException  {
        // 获取access_token
        String access_token= getAccessToken();
        // 获取用户信息
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/getuserinfo");
        OapiUserGetuserinfoRequest request = new OapiUserGetuserinfoRequest();
        request.setCode(requestAuthCode);
        request.setHttpMethod("GET");
        OapiUserGetuserinfoResponse response;
        try {
            response = client.execute(request, access_token);
        } catch (ApiException e) {
            e.printStackTrace();
            return null;
        }
        // 查询得到当前用户的userId
        String userId = response.getUserid();
        // 根据userId获取用户信息
        DingTalkClient clientDingTalkClient2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
        OapiV2UserGetRequest reqGetRequest = new OapiV2UserGetRequest();
        reqGetRequest.setUserid(userId);
        reqGetRequest.setLanguage("zh_CN");
        OapiV2UserGetResponse rspGetResponse = clientDingTalkClient2.execute(reqGetRequest, access_token);
        System.out.println(rspGetResponse.getBody());
        //获取用户信息
        OapiV2UserGetResponse.UserGetResponse result = rspGetResponse.getResult();
        System.out.println(result.getName());
    }

【钉钉免登录】(详解)钉钉接口,H5微应用,钉钉免登录及获取当前用户信息

核心代码


引入POM依赖:

<!-- 钉钉SDK -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>alibaba-dingtalk-service-sdk</artifactId>
    <version>1.0.1</version>
</dependency>

 接下来就是核心代码实现喽,可能会有点长哈

 Controller层

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiUserGetRequest;
import com.dingtalk.api.request.OapiUserGetuserinfoRequest;
import com.dingtalk.api.response.OapiUserGetResponse;
import com.dingtalk.api.response.OapiUserGetuserinfoResponse;
import com.taobao.api.ApiException;
import com.iot.daily.common.domain.vo.JsonResult;
import com.iot.daily.common.util.CommonUtil;
import com.iot.daily.dingding.config.DingAppConfig;
import com.iot.daily.dingding.config.DingUrlConstant;
import com.iot.daily.dingding.domain.ConfigDTO;
import com.iot.daily.dingding.domain.ServiceResult;
import com.iot.daily.dingding.domain.UserDTO;
import com.iot.daily.dingding.exception.DingtalkEncryptException;
import com.iot.daily.dingding.service.DingAuthService;
import com.iot.daily.dingding.service.TokenService;
import com.iot.daily.dingding.util.JsApiSignature;
import com.iot.daily.module.entity.DailyViewUser;
import com.iot.daily.organization.entity.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.Optional;
 
/**
 * <p>DingLoginController 此类用于:钉钉企业内部应用免登(H5微应用)</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月18日 15:06</p>
 * <p>@remark:钉钉企业内部微应用DEMO, 实现了身份验证(免登)功能</p>
 */
@Api(value = "dingAuthController", tags = "钉钉企业内部应用免登(H5微应用)")
@Slf4j
@Controller
@RequestMapping(value = "/ding")
public class DingAuthController {
 
    @Resource
    private TokenService tokenService;
 
    @Resource
    private DingAppConfig dingAppConfig;
 
    @Resource
    private DingAuthService dingAuthService;
 
    /**
     * 欢迎页面,通过 /welcome 访问,判断后端服务是否启动
     *
     * @return 字符串 welcome
     */
    @ApiOperation(value = "日报跳转的首页地址")
    @RequestMapping("/login")
    public String mobileLogin() {
        return "mobile/dinglogin";
    }
 
    /**
     * 欢迎页面,通过 /welcome 访问,判断后端服务是否启动
     *
     * @return 字符串 welcome
     */
    @ApiOperation(value = "日报跳转的首页地址")
    @RequestMapping("/index")
    public String mobileIndex() {
        return "mobile/index";
    }
 
    /**
     * 钉钉跳转到页面
     *
     * @param request  请求
     * @param response 响应
     * @return ModelAndView 页面
     */
    @RequestMapping("/toIndex")
    public ModelAndView toDingView(HttpServletRequest request, HttpServletResponse response) {
        String view = request.getParameter("view");
        Map<String, Object> pMap = CommonUtil.getParameterMap(request);
        return new ModelAndView(view, pMap);
    }
 
    /**
     * 钉钉用户登录,显示当前登录用户的userId和名称
     *
     * @param authCode 免登临时authCode
     * @return 当前用户
     */
    @ApiOperation(value = "钉钉用户登录,显示当前登录用户的userId和名称")
    @PostMapping("/avoidLogin")
    @ResponseBody
    public JsonResult login(@RequestBody String authCode, HttpServletRequest request) {
        String accessToken;
 
        // 获取accessToken
        ServiceResult<String> accessTokenSr = tokenService.getAccessToken();
        if (!accessTokenSr.isSuccess()) {
            return JsonResult.fail(Integer.parseInt(accessTokenSr.getCode()), accessTokenSr.getMessage());
        }
        accessToken = accessTokenSr.getResult();
 
        JSONObject jsonObject = JSONObject.parseObject(authCode);
        String getAuthCode = (String) jsonObject.get("authCode");
        // 获取用户userId
        ServiceResult<String> userIdSr = getUserInfo(accessToken, getAuthCode);
        if (!userIdSr.isSuccess()) {
            return JsonResult.fail(Integer.parseInt(userIdSr.getCode()), userIdSr.getMessage());
        }
 
        // 获取用户详情
        JsonResult userInfo = this.getUser(accessToken, userIdSr.getResult());
        UserDTO userDTO = (UserDTO) userInfo.getResult();
        log.info("根据accessToken和用户userId查询出的用户信息 userDTO = {}", userDTO);
        HttpSession session = request.getSession();
        User user = this.getUserFromUserDTO(userDTO);
        session.setAttribute("date", String.valueOf(System.currentTimeMillis()));
        session.setAttribute("user", user);
 
        return userInfo;
    }
 
    /**
     * 封装User对象
     *
     * @param userDTO userDTO对象
     * @return User对象
     */
    private User getUserFromUserDTO(UserDTO userDTO) {
 
        User user = new User();
        user.setUserName(userDTO.getUserName());
        user.setTrueName(userDTO.getTrueName());
        user.setUserId(userDTO.getUserId());
        user.setAgentSid(userDTO.getAgentSid());
        user.setCreateDate(userDTO.getCreateDate());
        user.setDingUserId(userDTO.getDingUserId());
        user.setDepartment(userDTO.getAgentName());
        user.setFid(userDTO.getFid());
        user.setHandset(userDTO.getHandset());
        user.setHeadPortrait(userDTO.getHeadPortrait());
        user.setIsAdmin(userDTO.getIsAdmin());
        user.setIsAlarm(userDTO.getIsAlarm());
        user.setIsLeader(userDTO.getIsLeader());
        user.setLoginCount(userDTO.getLoginCount());
        user.setLoginLastDate(userDTO.getLoginLastDate());
        user.setLoginLastIp(userDTO.getLoginLastIp());
        user.setOrgEmail(userDTO.getOrgEmail());
        user.setPosition(userDTO.getPosition());
        user.setPassword(userDTO.getPassword());
        user.setPwdLastDate(userDTO.getPwdLastDate());
        user.setRoleId(userDTO.getRoleId());
        user.setUserLevel(userDTO.getUserLevel());
        user.setUserState(userDTO.getUserState());
 
        return user;
    }
 
    /**
     * 访问/user/getuserinfo接口获取用户userId
     *
     * @param accessToken access_token
     * @param authCode    临时授权码
     * @return 用户userId或错误信息
     */
    private ServiceResult<String> getUserInfo(String accessToken, String authCode) {
        DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_GET_USER_INFO);
        OapiUserGetuserinfoRequest request = new OapiUserGetuserinfoRequest();
        request.setCode(authCode);
        request.setHttpMethod("GET");
 
        OapiUserGetuserinfoResponse response;
        try {
            response = client.execute(request, accessToken);
        } catch (ApiException e) {
            log.error("Failed to {}", DingUrlConstant.URL_GET_USER_INFO, e);
            return ServiceResult.failure(e.getErrCode(), "Failed to getUserInfo: " + e.getErrMsg());
        }
        if (!response.isSuccess()) {
            return ServiceResult.failure(response.getErrorCode(), response.getErrmsg());
        }
 
        return ServiceResult.success(response.getUserid());
    }
 
    /**
     * 访问/user/get 获取用户名称
     *
     * @param accessToken access_token
     * @param userId      用户userId
     * @return 用户名称或错误信息
     */
    private JsonResult getUser(String accessToken, String userId) {
        DingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_USER_GET);
        OapiUserGetRequest request = new OapiUserGetRequest();
        request.setUserid(userId);
        request.setHttpMethod("GET");
 
        OapiUserGetResponse response;
        try {
            response = client.execute(request, accessToken);
        } catch (ApiException e) {
            log.error("Failed to {}", DingUrlConstant.URL_USER_GET, e);
            return JsonResult.fail(Integer.parseInt(e.getErrCode()), "Failed to getUserName: " + e.getErrMsg());
        }
 
        UserDTO user = this.assembleUserDTO(response, accessToken);
 
        return JsonResult.ok(user);
    }
 
    /**
     * 封装返回的用户信息
     *
     * @param response    response
     * @param accessToken accessToken
     * @return 用户信息
     */
    private UserDTO assembleUserDTO(OapiUserGetResponse response, String accessToken) {
 
        UserDTO user = new UserDTO();
        String userid = response.getUserid();
        String mobile = response.getMobile();
        try {
            DailyViewUser dailyViewUser = Optional.ofNullable(dingAuthService.getUserByUserId(userid))
                    .orElse(dingAuthService.getUserByUsername(mobile));
 
            BeanUtil.copyProperties(dailyViewUser, user);
            user.setAccessToken(accessToken);
            user.setName(response.getName());
            user.setAvatar(response.getAvatar());
        } catch (Exception e) {
            log.error("E|DingAuthController|assembleUserDTO()|根据用户钉钉userId或者用户钉钉手机号mobile查询用户信息失败!");
        }
 
        return user;
    }
 
    @ApiOperation(value = "钉钉配置接口")
    @PostMapping("/config")
    @ResponseBody
    public JsonResult config(HttpServletRequest request) {
        ConfigDTO config = new ConfigDTO();
 
        String url = request.getRequestURL().toString();
        String queryString = request.getQueryString();
        if (queryString != null) {
            url = url + queryString;
        }
 
        ServiceResult<String> jsTicketSr = tokenService.getJsTicket();
        if (!jsTicketSr.isSuccess()) {
            return JsonResult.fail(Integer.parseInt(jsTicketSr.getCode()), jsTicketSr.getMessage());
        }
 
        config.setAgentId(dingAppConfig.getAgentId());
        config.setCorpId(dingAppConfig.getCorpId());
        config.setJsticket(jsTicketSr.getResult());
        config.setNonceStr(JsApiSignature.genNonce());
        config.setTimeStamp(System.currentTimeMillis() / 1000);
        String sign;
        try {
            sign = JsApiSignature.sign(url, config.getNonceStr(), config.getTimeStamp(), config.getJsticket());
        } catch (DingtalkEncryptException e) {
            return JsonResult.fail(e.getCode(), e.getMessage());
        }
        config.setSignature(sign);
        return JsonResult.ok(config);
    }
}

 Service层以及ServiceImpl

 
import com.iot.daily.module.entity.DailyViewUser;
 
/**
 * <p>DingAuthService 此接口用于:</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月20日 15:56</p>
 * <p>@remark:</p>
 */
public interface DingAuthService {
 
    /**
     * 根据用户手机号查询是否是公司员工
     *
     * @param userId 钉钉用户id
     * @return UIOT员工信息
     */
    DailyViewUser getUserByUserId(String userId);
 
    /**
     * 根据用户手机号查询是否是公司员工
     *
     * @param mobile 钉钉用户手机号
     * @return UIOT员工信息
     */
    DailyViewUser getUserByUsername(String mobile);
}
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.request.OapiGetJsapiTicketRequest;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.response.OapiGetJsapiTicketResponse;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.taobao.api.ApiException;
import com.iot.daily.common.constant.CommonConstants;
import com.iot.daily.common.util.cache.RedisCache;
import com.iot.daily.dingding.config.DingAppConfig;
import com.iot.daily.dingding.config.DingUrlConstant;
import com.iot.daily.dingding.domain.ServiceResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
 
/**
 * <p>DingLoginController 此类用于:获取access_token 和 jsTicket方法</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月18日 15:06</p>
 * <p>@remark:钉钉企业内部微应用DEMO, 实现了身份验证(免登)功能</p>
 */
@Slf4j
@Service
public class TokenService {
 
    @Resource
    private RedisCache redisCache;
 
    /**
     * 缓存时间:一小时50分钟
     */
    private static final long CACHE_TTL = 60 * 55 * 2;
 
    @Resource
    private DingAppConfig dingAppConfig;
 
    /**
     * 在此方法中,为了避免频繁获取access_token,
     * 在距离上一次获取access_token时间在两个小时之内的情况,
     * 将直接从持久化存储中读取access_token
     * <p>
     * 因为access_token和jsapi_ticket的过期时间都是7200秒
     * 所以在获取access_token的同时也去获取了jsapi_ticket
     * 注:jsapi_ticket是在前端页面JSAPI做权限验证配置的时候需要使用的
     * 具体信息请查看开发者文档--权限验证配置
     *
     * @return accessToken 或错误信息
     */
    public ServiceResult<String> getAccessToken() {
        // 从持久化存储中读取
        String accessToken = redisCache.get(CommonConstants.DAILY_DING_ACCESS_TOKEN);
        log.info("从Redis缓存中获取到的accessToken = {}", accessToken);
        if (accessToken != null) {
            return ServiceResult.success(accessToken);
        }
 
        DefaultDingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_GET_TOKEN);
        OapiGettokenRequest request = new OapiGettokenRequest();
        OapiGettokenResponse response;
 
        request.setAppkey(dingAppConfig.getAppKey());
        request.setAppsecret(dingAppConfig.getAppSecret());
        request.setHttpMethod("GET");
 
        try {
            response = client.execute(request);
        } catch (ApiException e) {
            log.error("getAccessToken failed", e);
            return ServiceResult.failure(e.getErrCode(), e.getErrMsg());
        }
 
        accessToken = response.getAccessToken();
        log.info("向Redis缓存中存取accessToken = {}", accessToken);
        redisCache.set(CommonConstants.DAILY_DING_ACCESS_TOKEN, accessToken, CACHE_TTL, TimeUnit.SECONDS);
 
        return ServiceResult.success(accessToken);
    }
 
    /**
     * 获取JSTicket, 用于js的签名计算
     * 正常的情况下,jsapi_ticket的有效期为7200秒,所以开发者需要在某个地方设计一个定时器,定期去更新jsapi_ticket
     *
     * @return jsTicket或错误信息
     */
    public ServiceResult<String> getJsTicket() {
        // 从持久化存储中读取
        String ticket = redisCache.get(CommonConstants.DAILY_DING_JS_TICKET);
        if (ticket != null) {
            return ServiceResult.success(ticket);
        }
 
        String accessToken;
        ServiceResult<String> tokenSr = getAccessToken();
        if (!tokenSr.isSuccess()) {
            return ServiceResult.failure(tokenSr.getCode(), tokenSr.getMessage());
        }
        accessToken = tokenSr.getResult();
 
        DefaultDingTalkClient client = new DefaultDingTalkClient(DingUrlConstant.URL_GET_JSTICKET);
        OapiGetJsapiTicketRequest request = new OapiGetJsapiTicketRequest();
        OapiGetJsapiTicketResponse response;
 
        request.setHttpMethod("GET");
 
        try {
            response = client.execute(request, accessToken);
        } catch (ApiException e) {
            log.error("getAccessToken failed", e);
            return ServiceResult.failure(e.getErrCode(), e.getErrMsg());
        }
 
        if (!response.isSuccess()) {
            return ServiceResult.failure(response.getErrorCode(), response.getErrmsg());
        }
        ticket = response.getTicket();
        redisCache.set(CommonConstants.DAILY_DING_JS_TICKET, ticket, CACHE_TTL, TimeUnit.SECONDS);
 
        return ServiceResult.success(ticket);
    }
}

上面TokenService类中的两个常量分别是:

/**
 * 日报系统钉钉JSTICKET命名空间和ACCESSTOKEN命名空间
 */
public static final String DAILY_DING_JS_TICKET = "dailyApplication:login:ticket:";
public static final String DAILY_DING_ACCESS_TOKEN = "dailyApplication:login:accessToken:";
import com.iot.daily.dingding.service.DingAuthService;
import com.iot.daily.module.dao.DailyViewUserMapper;
import com.iot.daily.module.entity.DailyViewUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
 
/**
 * <p>DingAuthServiceImpl 此类用于:</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月20日 15:56</p>
 * <p>@remark:</p>
 */
@Slf4j
@Service
public class DingAuthServiceImpl implements DingAuthService {
 
    @Resource
    private DailyViewUserMapper dailyViewUserMapper;
 
    @Override
    public DailyViewUser getUserByUserId(String userId) {
 
        if (userId == null) {
            log.error("E|DingAuthServiceImpl|getUserByUserId()|根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
            return new DailyViewUser();
        }
 
        DailyViewUser dailyViewUser = dailyViewUserMapper.getDailyViewUserByUserId(userId);
 
        log.info("E|DingAuthServiceImpl|getUserByUserId()|根据用户手机号查询UIOT用户,userId = {}", userId);
 
        return dailyViewUser;
    }
 
    @Override
    public DailyViewUser getUserByUsername(String mobile) {
 
        if (mobile == null) {
            log.error("E|DingAuthServiceImpl|getUserByUsername()|根据用户手机号查询UIOT用户时,当前钉钉用户id为空!");
            return new DailyViewUser();
        }
 
        DailyViewUser dailyViewUser = dailyViewUserMapper.getDailyViewUserByUsername(mobile);
 
        log.info("E|DingAuthServiceImpl|getUserByUsername()|根据用户手机号查询UIOT用户,userId = {}", mobile);
 
        return dailyViewUser;
    }
}

相关配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
 
/**
 * <p>DingAppConfig 此类用于:应用凭证配置</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月18日 17:25</p>
 * <p>@remark:</p>
 */
@Data
@Configuration
public class DingAppConfig {
 
    @Value("${dingtalk.app_key}")
    private String appKey;
 
    @Value("${dingtalk.app_secret}")
    private String appSecret;
 
    @Value("${dingtalk.agent_id}")
    private String agentId;
 
    @Value("${dingtalk.corp_id}")
    private String corpId;
}
/**
 * 钉钉开放接口网关常量
 *
 * @author Administrator
 */
public class DingUrlConstant {
 
    private static final String HOST = "https://oapi.dingtalk.com";
 
    /**
     * 获取access_token url
     */
    public static final String URL_GET_TOKEN = HOST + "/gettoken";
 
    /**
     * 获取jsapi_ticket url
     */
    public static final String URL_GET_JSTICKET = HOST + "/get_jsapi_ticket";
 
    /**
     * 通过免登授权码获取用户信息 url
     */
    public static final String URL_GET_USER_INFO = HOST + "/user/getuserinfo";
 
    /**
     * 根据用户id获取用户详情 url
     */
    public static final String URL_USER_GET = HOST + "/user/get";
 
    /**
     * 获取部门列表 url
     */
    public static final String URL_DEPARTMENT_LIST = HOST + "/department/list";
 
    /**
     * 获取部门用户 url
     */
    public static final String URL_USER_SIMPLELIST = HOST + "/user/simplelist";
 
}
 
import java.io.Serializable;
 
/**
 * <p>DingLoginController 此类用于:service层返回对象列表封装</p>
 * <p>@author:hujm</p>
 * <p>@date:2021年05月18日 15:06</p>
 * <p>@remark:service层返回对象列表封装</p>
 */
public class ServiceResult<T> implements Serializable {
 
    private boolean success = false;
 
    private String code;
 
    private String message;
 
    private T result;
 
    private ServiceResult() {
    }
 
    public static <T> ServiceResult<T> success(T result) {
        ServiceResult<T> item = new ServiceResult<T>();
        item.success = true;
        item.result = result;
        item.code = "0";
        item.message = "success";
        return item;
    }
 
    public static <T> ServiceResult<T> failure(String errorCode, String errorMessage) {
        ServiceResult<T> item = new ServiceResult<T>();
        item.success = false;
        item.code = errorCode;
        item.message = errorMessage;
        return item;
    }
 
    public static <T> ServiceResult<T> failure(String errorCode) {
        ServiceResult<T> item = new ServiceResult<T>();
        item.success = false;
        item.code = errorCode;
        item.message = "failure";
        return item;
    }
 
    public boolean hasResult() {
        return result != null;
    }
 
    public boolean isSuccess() {
        return success;
    }
 
    public T getResult() {
        return result;
    }
 
    public String getCode() {
        return code;
    }
 
    public String getMessage() {
        return message;
    }
 
}

免登录注意事项

钉钉免登录应用注意事项:

   1.创建应用; 
   2.配置出口IP与访问首页;
   3.开放权限(手机号码信息、通讯录部门信息读权限、成员信息读权限、通讯录部门成员读权限);
 

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果您下载了本程序,但是该程序存在问题无法运行,那么您可以选择退款或者寻求我们的帮助(如果找我们帮助的话,是需要追加额外费用的)。另外,您不会使用资源的话(这种情况不支持退款),也可以找我们帮助(需要追加额外费用) 信小程序是腾讯公司基于信平台推出的一种轻量级应用形态,它无需用户下载安装即可在信内直接使用。自2017年正式上线以来,小程序凭借其便捷性、易获取性和出色的用户体验迅速获得市场认可,并成为连接线上线下服务的重要桥梁。 小程序的核心特点包括: 零安装:用户只需通过信扫一扫或搜索功能,即可打开和使用小程序,大大降低了用户的使用门槛和手机存储空间压力。 速度快:加载速度相较于传统的HTML5网页更快,依托于信强大的基础设施,能够实现近乎原生应用的流畅体验。 跨平台兼容:开发者一次开发,即可在多种终端设备上运行,除了复杂的适配工作,大大提高了开发效率。 社交属性强:小程序可以无缝嵌入信生态,支持分享至聊天窗口、朋友圈等社交场景,有利于用户间的传播和裂变增长。 丰富接口能力:提供丰富的API接口,可调用信支付、位置服务、用户身份识别等多种功能,方便企业进行商业服务的集成与拓展。 目前,信小程序已经覆盖了电商购物、生活服务、娱乐休闲、教育学习、工具助手等多个领域,为数以亿计的用户提供便捷的服务入口,也为众多商家和开发者提供了新的商业模式和创业机会。随着技术的不断升级和完善,小程序已成为现代移动互联网生态中不可或缺的一部分。
如果您下载了本程序,但是该程序存在问题无法运行,那么您可以选择退款或者寻求我们的帮助(如果找我们帮助的话,是需要追加额外费用的)。另外,您不会使用资源的话(这种情况不支持退款),也可以找我们帮助(需要追加额外费用) 信小程序是腾讯公司基于信平台推出的一种轻量级应用形态,它无需用户下载安装即可在信内直接使用。自2017年正式上线以来,小程序凭借其便捷性、易获取性和出色的用户体验迅速获得市场认可,并成为连接线上线下服务的重要桥梁。 小程序的核心特点包括: 零安装:用户只需通过信扫一扫或搜索功能,即可打开和使用小程序,大大降低了用户的使用门槛和手机存储空间压力。 速度快:加载速度相较于传统的HTML5网页更快,依托于信强大的基础设施,能够实现近乎原生应用的流畅体验。 跨平台兼容:开发者一次开发,即可在多种终端设备上运行,除了复杂的适配工作,大大提高了开发效率。 社交属性强:小程序可以无缝嵌入信生态,支持分享至聊天窗口、朋友圈等社交场景,有利于用户间的传播和裂变增长。 丰富接口能力:提供丰富的API接口,可调用信支付、位置服务、用户身份识别等多种功能,方便企业进行商业服务的集成与拓展。 目前,信小程序已经覆盖了电商购物、生活服务、娱乐休闲、教育学习、工具助手等多个领域,为数以亿计的用户提供便捷的服务入口,也为众多商家和开发者提供了新的商业模式和创业机会。随着技术的不断升级和完善,小程序已成为现代移动互联网生态中不可或缺的一部分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值