手搓钉钉小程序登陆(借助RuoYi框架)

文章讲述如何在开发钉钉小程序的时候,实现免登陆功能,本系统基于RuoYi SpringBoot3版本测试,有些类找不到可以去RuoYi官网查看。

前言

钉钉小程序开发时,需要实现免登陆功能,即用户在钉钉小程序无需账号密码登录,后续进入小程序,也不需要再次登录。

主要流程

  1. 在钉钉小程序端获取authCode
  2. 将authCode发送到服务器
  3. 后端服务器获取Access Token,通过authCode获取获取userId,最后通过userId获取用户信息(钉钉)
  4. 拿到钉钉用户信息后,创建系统用户,然后创建用户ID和钉钉用户标识的关联数据
  5. 创建JWT返回给客户端

具体实现

1. 在钉钉小程序端获取authCode

调用 dd.getAuthCode,获取应用免登授权码
文档地址:https://open.dingtalk.com/document/orgapp/jsapi-get-auth-code

const { authCode } = await dd.getAuthCode();
2.将authCode发送到服务器

使用httpRequest发送authCode给服务器,在钉钉小程序发送请求需要JSON.stringify()一下
文档地址:https://open.dingtalk.com/document/orgapp/jsapi-http-request

const res = await dd.httpRequest({
    url: 'http://note.studiogm.cn/dingtalk/login',
    data: JSON.stringify({
        code: authCode
    }),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    timeout: 30000,
    dataType
})
3. 后端服务器获取Access Token,通过authCode获取获取userId,最后通过userId获取用户信息(钉钉)

使用SpringBoot做演示

实体类文件SysAuthUser.java

package com.gm.system.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@TableName("sys_auth_user")
public class SysAuthUser {

    /**
     * 授权ID
     */
    @TableId(value = "auth_id", type = IdType.AUTO)
    private Long authId;

    /**
     * 第三方平台用户唯一ID
     */
    private String uuid;

    /**
     * 系统用户ID
     */
    private Long userId;

    /**
     * 登录账号
     */
    private String userName;

    /**
     * 用户昵称
     */
    private String nickName;

    /**
     * 头像地址
     */
    private String avatar;

    /**
     * 用户邮箱
     */
    private String email;

    /**
     * 用户来源
     */
    private String source;

    /**
     * 创建时间
     */
    private Date createTime;

}

AuthUserLoginDTO.java

package com.gm.dingtalk.domain.dto;

import lombok.Data;

@Data
public class AuthUserLoginDTO {
    private String code;
}

DingTalkLoginVO.java

package com.gm.dingtalk.domain.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DingTalkLoginVO {
    /**
     * 设备ID
     */
    private String deviceId;

    /**
     * 用户名称
     */
    private String name;

    /**
     * 是否是管理员
     */
    private Boolean sys;

    /**
     * 系统级别
     */
    private Integer sysLevel;

    /**
     * 用户在钉钉的唯一标识
     */
    private String unionid;

    /**
     * 用户ID
     */
    private String userid;
}

Controller文件 DingTalkLoginController.java

package com.gm.web.controller.dingtalk;

import com.gm.common.constant.Constants;
import com.gm.common.constant.HttpStatus;
import com.gm.common.core.controller.BaseController;
import com.gm.common.core.domain.AjaxResult;
import com.gm.common.exception.ServiceException;
import com.gm.dingtalk.domain.dto.AuthUserLoginDTO;
import com.gm.dingtalk.domain.vo.DingTalkLoginVO;
import com.gm.dingtalk.service.AuthUserService;
import org.springframework.beans.factory.annotation.Autowired;
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.RestController;

@RestController
@RequestMapping("/dingtalk")
public class DingTalkLoginController extends BaseController {

    @Autowired
    private AuthUserService authUserService;

    /**
     * 钉钉登录
     * @param authUserLoginDTO
     * @return
     * @throws Exception
     */
    @PostMapping("/login")
    public AjaxResult login(@RequestBody AuthUserLoginDTO authUserLoginDTO) throws Exception{
        if ( authUserLoginDTO.getCode() == null)  {
            throw new ServiceException("code不能为空", HttpStatus.BAD_REQUEST);
        }

        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = authUserService.loginByDingCode(authUserLoginDTO.getCode());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }

}

Service文件 AuthUserService.java

package com.gm.dingtalk.service;

import com.aliyun.dingtalkoauth2_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.GetTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetTokenResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiV2UserGetuserinfoRequest;
import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
import com.gm.common.constant.HttpStatus;
import com.gm.common.core.domain.entity.SysUser;
import com.gm.common.core.domain.model.LoginUser;
import com.gm.common.exception.ServiceException;
import com.gm.common.utils.DateUtils;
import com.gm.common.utils.SecurityUtils;
import com.gm.common.utils.ip.IpUtils;
import com.gm.dingtalk.config.DingTalkConfig;
import com.gm.framework.web.service.SysPermissionService;
import com.gm.system.domain.SysAuthUser;
import com.gm.dingtalk.domain.vo.DingTalkLoginVO;
import com.gm.system.mapper.SysAuthUserMapper;
import com.gm.system.service.ISysConfigService;
import com.gm.system.service.ISysUserService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gm.framework.web.service.TokenService;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Service
public class AuthUserService extends ServiceImpl<SysAuthUserMapper, SysAuthUser> implements IService<SysAuthUser> {

    @Autowired
    private SysAuthUserMapper sysAuthUserMapper;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private SysPermissionService permissionService;

    @Autowired
    private DingTalkConfig dingTalkConfig;

    @Autowired
    private ISysConfigService sysConfigService;

    public static Client createClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new Client(config);
    }

    /**
     * 钉钉登录
     *
     * @param code
     * @return nil
     * @throws Exception
     */
    @Transactional
    public String loginByDingCode(String code) throws Exception {
        try {
            // 通过code获取用户信息
            DingTalkLoginVO dingTalkLoginVO = getDingTalkLoginVOByCode(code);
            // 判断当前用户是否有授权信息
            SysAuthUser sysAuthUser = sysAuthUserMapper.selectByUuid(dingTalkLoginVO.getUnionid());
            if (sysAuthUser != null) {
                // 该用户已有授权信息
                Long userId = sysAuthUser.getUserId();
                SysUser user = userService.selectUserById(userId);
                return genToken(user);
            } else {
                // 第一次授权
                // 注册用户
                SysUser sysUser = createSysUser(dingTalkLoginVO);
                // 新增授权用户关联信息
                SysAuthUser newAuthUser = SysAuthUser.builder()
                        .userId(sysUser.getUserId())
                        .uuid(dingTalkLoginVO.getUnionid())
                        .userName(dingTalkLoginVO.getName())
                        .nickName(sysUser.getUserName())
                        .source("钉钉")
                        .createTime(new Date())
                        .build();
                int insert = sysAuthUserMapper.insert(newAuthUser);
                // 判断失败
                if (insert <= 0) {
                     throw new ServiceException("用户授权失败", HttpStatus.ERROR);
                }
                return genToken(sysUser);
            }
        } catch (TeaException err) {
            // err.code 和 err.message 获取异常信息
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
                throw new ServiceException(err.message, HttpStatus.ERROR);
            }

        } catch (Exception _err) {
            //  获取异常信息
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
                throw new ServiceException(err.message, HttpStatus.ERROR);
            }
        }
        return null;
    }

    /**
     *  获取钉钉用户信息
     * @param dingTalkLoginVO
     * @return
     */
    @NotNull
    private SysUser createSysUser(DingTalkLoginVO dingTalkLoginVO) {
        SysUser sysUser = new SysUser();
        sysUser.setNickName(dingTalkLoginVO.getName());
        sysUser.setUserName("gm-" + dingTalkLoginVO.getUserid());
        sysUser.setStatus("0");
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        sysUser.setCreateBy("owner");
        sysUser.setPassword(SecurityUtils.encryptPassword(sysConfigService.selectConfigByKey("sys.user.initPassword")));
        sysUser.setCreateTime(DateUtils.getNowDate());
        sysUser.setDelFlag("0");
        sysUser.setDeptId(100L); // 根部门
        boolean b = userService.registerUser(sysUser);
        if (!b) {
            throw new ServiceException("注册用户失败");
        }
        return sysUser;
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    /**
     * 生成token
     * @param user
     * @return
     */
    public String genToken(SysUser user) {
        LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));;
        recordLoginInfo(user.getUserId());
        return tokenService.createToken(loginUser);
    }

    /**
     *  获取钉钉用户信息
     * @param code
     * @return
     * @throws Exception
     */
    public DingTalkLoginVO getDingTalkLoginVOByCode(String code) throws Exception {
        // 创建客户端
        Client client = createClient();
        GetTokenRequest getTokenRequest = new GetTokenRequest()
                .setClientId(dingTalkConfig.getClientId())
                .setClientSecret(dingTalkConfig.getClientSecret())
                .setGrantType("client_credentials");
        // 获取AccessToken
        GetTokenResponse token = client.getToken(dingTalkConfig.getCorpId(), getTokenRequest);
        String access_token = token.body.accessToken;
        // 获取user_id
        DingTalkClient client1 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
        OapiV2UserGetuserinfoRequest req = new OapiV2UserGetuserinfoRequest();
        req.setCode(code);
        OapiV2UserGetuserinfoResponse rsp = client1.execute(req, access_token);
        // 转成DingTalkLoginVO
        DingTalkLoginVO dingTalkLoginVO = new DingTalkLoginVO();
        BeanUtils.copyProperties(rsp.getResult(), dingTalkLoginVO);
        return dingTalkLoginVO;
    }

}

Mapper文件 AuthUserMapper.java

package com.gm.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gm.system.domain.SysAuthUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface SysAuthUserMapper extends BaseMapper<SysAuthUser> {

    @Select( "select * from sys_auth_user where uuid = #{uuid}")
     SysAuthUser selectByUuid(String uuid);

}

配置类文件 DingTalkConfig.java

package com.gm.dingtalk.config;

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

@Data
@Component
@ConfigurationProperties(prefix = "dingtalk")
public class DingTalkConfig {

    /**
     * 企业ID corpId
     */
    private String corpId;

     /**
     * 应用ID clientId
     */
     private String clientId;

      /**
     * 应用密钥 clientSecret
     */
      private String clientSecret;
}

配置文件 application.yml

dingtalk:
  corpId: xxx
  clientId: xxx
  clientSecret: xxx

原文地址(本人站点):https://codels.gmcoder.cn/blog/ding-talk-login

如果对您有帮助的话请点点关注,感谢支持

你可能感兴趣的:(钉钉生态创业者专栏,钉钉,小程序)