本文正在参加「金石方案」

1 前言

上一年6月份写了《uni-app微信小程序授权登录实操演战》这篇文章,从前端的角度完成了微信小程序授权登陆。

本次结合开源若依结构RuoYi与uni-app集成微信小程序授权登陆,完成前后端数据传输、处理的过程。

注:《uni-app微信小程序授权登录实操演战》这篇文章撰写时,微信登陆接口供给了取得用户昵称和头像,目前该接口已中止供给昵称和头像(小程序用户头像昵称获取规矩调整公告) ,故本文中若依用户创立时的昵称和用户名随机生成,可在个人中心事务开发中微信开发渠道供给(头像昵称填写)能力来完成。

1.1 环境预备

  • **下载源码:**若依结构RuoYi-Vue版别 :gitee.com/y_project/R…

  • **开发东西:**IDEA、HbuilderX、Navicat、RDM

  • **中间件:**Redis、Mysql8.0

1.2 登陆流程

若依框架+uni-app实现微信小程序授权登录

2 前端代码-UniApp

2.1 创立uniapp项目,并开启OAuth登陆鉴权微信登陆

双击manifest.json进行设置

若依框架+uni-app实现微信小程序授权登录

2.1 添加微信登陆授权按钮

<button type="default" @click="wxLogin">微信登陆</button>

2.2 创立wxLogin()办法

wxLogin() {
    // 获取服务供货商
    uni.getProvider({
        service: 'oauth',
        success: (res) => {
            if (~res.provider.indexOf("weixin")) {
                // uni微信登陆
                uni.login({
                    provider: 'weixin',
                    success: (loginRes) => {
                        // 获取用户信息
                        uni.getUserInfo({
                            success: (resInfo) => {
                                // 微信登录 code:jscode  encryptedIv:偏移量 													encryptedData: 加密数据
                                uni.request({
                                    url: "http://localhost:8080/wxLogin", //后端接口
                                    method: 'POST',
                                    data: {
                                        code: loginRes.code,
                                        encryptedIv: resInfo.iv,
                                        encryptedData: resInfo
                                        .encryptedData
                                    },
                                    success: (wxLoginRes) => {
                                        console.log(
                                            "wxLoginRes: ",
                                            wxLoginRes);
                                    }
                                })
                            }
                        })
                    }
                })
            }
        }
    })
}

3 后端代码-若依

3.1 application.yml配置文件中,添加微信小程序appId和appSecret

ruoyi-admin/src/main/resources/application.yml

wx-app:
  appId: wxae5813756948397b
  appSecret: 662faf6ab9e7d635bcda30478f10b333

3.2 在数据库sys_user表中,添加open_id和union_id字段

OpenID: 普通用户的标识,对当时开发者帐号仅有。一个openid对应一个公众号或小程序;
UnionID: 用户一致标识,针对一个微信敞开渠道帐号下的运用,同一用户的unionid是仅有的。

本例中,我们只用到open_id

依据事务需要,如多个运用可接入一致标识UnionID。

若依框架+uni-app实现微信小程序授权登录

3.3 在SysUser类中新增两个特点

ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java

/** unionId */
private String unionId;
/** openId */
private String openId;
public String getUnionId() {
    return unionId;
}
public void setUnionId(String unionId) {
    this.unionId = unionId;
}
public String getOpenId() {
    return openId;
}
public void setOpenId(String openId) {
    this.openId = openId;
}

toString()办法中参加open_id

@Override
public String toString() {
    return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
        .append("userId", getUserId())
        .append("deptId", getDeptId())
        .append("userName", getUserName())
        .append("nickName", getNickName())
        .append("email", getEmail())
        .append("phonenumber", getPhonenumber())
        .append("sex", getSex())
        .append("avatar", getAvatar())
        .append("password", getPassword())
        .append("status", getStatus())
        .append("delFlag", getDelFlag())
        .append("loginIp", getLoginIp())
        .append("loginDate", getLoginDate())
        .append("createBy", getCreateBy())
        .append("createTime", getCreateTime())
        .append("updateBy", getUpdateBy())
        .append("updateTime", getUpdateTime())
        .append("remark", getRemark())
        .append("dept", getDept())
        .append("openId", getOpenId())
        .toString();
}

3.4 SysUserMapper新增selectWxUserByOpenId

ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java

/**
* 依据openId查询用户信息
* @param openId
* @return
*/
public SysUser selectWxUserByOpenId(String openId);

3.5 SysUserMapper.xml改造

ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

resultMap添加openId的字段映射

<resultMap type="SysUser" id="SysUserResult">
    <id     property="userId"       column="user_id"      />
    <result property="deptId"       column="dept_id"      />
    <result property="userName"     column="user_name"    />
    <result property="nickName"     column="nick_name"    />
    <result property="email"        column="email"        />
    <result property="phonenumber"  column="phonenumber"  />
    <result property="sex"          column="sex"          />
    <result property="avatar"       column="avatar"       />
    <result property="password"     column="password"     />
    <result property="status"       column="status"       />
    <result property="delFlag"      column="del_flag"     />
    <result property="loginIp"      column="login_ip"     />
    <result property="loginDate"    column="login_date"   />
    <result property="createBy"     column="create_by"    />
    <result property="createTime"   column="create_time"  />
    <result property="updateBy"     column="update_by"    />
    <result property="updateTime"   column="update_time"  />
    <result property="openId"       column="open_id"  />
    <result property="remark"       column="remark"       />
    <association property="dept"    column="dept_id" javaType="SysDept" resultMap="deptResult" />
    <collection  property="roles"   javaType="java.util.List"           resultMap="RoleResult" />
</resultMap>

selectUserList添加对u.open_id字段的查询。(可选,供PC端用户列表中查询用户open_id)

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
    select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time,u.open_id, u.remark, d.dept_name, d.leader from sys_user u
    left join sys_dept d on u.dept_id = d.dept_id
    where u.del_flag = '0'
    <if test="userId != null and userId != 0">
        AND u.user_id = #{userId}
    </if>
    <if test="userName != null and userName != ''">
        AND u.user_name like concat('%', #{userName}, '%')
    </if>
    <if test="status != null and status != ''">
        AND u.status = #{status}
    </if>
    <if test="phonenumber != null and phonenumber != ''">
        AND u.phonenumber like concat('%', #{phonenumber}, '%')
    </if>
    <if test="params.beginTime != null and params.beginTime != ''"><!-- 开端时刻检索 -->
        AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
    </if>
    <if test="params.endTime != null and params.endTime != ''"><!-- 结束时刻检索 -->
        AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
    </if>
    <if test="deptId != null and deptId != 0">
        AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
    </if>
    <!-- 数据规模过滤 -->
    ${params.dataScope}
</select>

新增selectWxUserByOpenId

<select id="selectWxUserByOpenId" parameterType="String" resultMap="SysUserResult">
    <include refid="selectUserVo" />
    where u.open_id = #{openId} and u.del_flag = '0'
</select>

修改insertUser,添加open_id相关句子。

<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
    insert into sys_user(
    <if test="userId != null and userId != 0">user_id,</if>
    <if test="deptId != null and deptId != 0">dept_id,</if>
    <if test="userName != null and userName != ''">user_name,</if>
    <if test="nickName != null and nickName != ''">nick_name,</if>
    <if test="email != null and email != ''">email,</if>
    <if test="avatar != null and avatar != ''">avatar,</if>
    <if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
    <if test="sex != null and sex != ''">sex,</if>
    <if test="password != null and password != ''">password,</if>
    <if test="status != null and status != ''">status,</if>
    <if test="createBy != null and createBy != ''">create_by,</if>
    <if test="openId != null and openId != ''">open_id,</if>
    <if test="remark != null and remark != ''">remark,</if>
    create_time
    )values(
    <if test="userId != null and userId != ''">#{userId},</if>
    <if test="deptId != null and deptId != ''">#{deptId},</if>
    <if test="userName != null and userName != ''">#{userName},</if>
    <if test="nickName != null and nickName != ''">#{nickName},</if>
    <if test="email != null and email != ''">#{email},</if>
    <if test="avatar != null and avatar != ''">#{avatar},</if>
    <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
    <if test="sex != null and sex != ''">#{sex},</if>
    <if test="password != null and password != ''">#{password},</if>
    <if test="status != null and status != ''">#{status},</if>
    <if test="createBy != null and createBy != ''">#{createBy},</if>
    <if test="openId != null and openId != ''">#{openId},</if>
    <if test="remark != null and remark != ''">#{remark},</if>
    sysdate()
    )
</insert>

3.5 ApplicationConfig添加 HTTP 恳求东西配置

ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java

/**
* 长途调用  HTTP 恳求东西
*/
@Bean
public RestTemplate RestTemplate(){
    return new RestTemplate();
}

3.5 新增WxLoginBody类

ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/WxLoginBody.java

package com.ruoyi.common.core.domain.model;
/**
 * 微信用户登录对象
 *
 * @author ruoyi
 */
public class WxLoginBody
{
    /**
     * 暂时登陆凭据 code 只能运用一次
     */
    private String code;
    /**
     * 偏移量
     */
    private String encryptedIv;
    /**
     * 加密数据
     */
    private String encryptedData;
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getEncryptedIv() {
        return encryptedIv;
    }
    public void setEncryptedIv(String encryptedIv) {
        this.encryptedIv = encryptedIv;
    }
    public String getEncryptedData() {
        return encryptedData;
    }
    public void setEncryptedData(String encryptedData) {
        this.encryptedData = encryptedData;
    }
}

3.5 SysLoginController,新增接口wxLogin和AES解密办法

ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@Autowired
private WxAppConfig wxAppConfig;
@PostMapping("/wxLogin")
public AjaxResult wxLogin(@RequestBody WxLoginBody wxLoginBody) {
    String code = wxLoginBody.getCode();
    //秘钥
    String encryptedIv = wxLoginBody.getEncryptedIv();
    //加密数据
    String encryptedData = wxLoginBody.getEncryptedData();
    //向微信服务器发送恳求获取用户信息
    String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppConfig.getAppId() + "&secret=" + wxAppConfig.getAppSecret() + "&js_code=" + code + "&grant_type=authorization_code";
    String res = restTemplate.getForObject(url, String.class);
    JSONObject jsonObject = JSONObject.parseObject(res);
    //获取session_key和openid
    String sessionKey = jsonObject.getString("session_key");
    String openid = jsonObject.getString("openid");
    //解密
    String decryptResult = "";
    try {
        //假如没有绑定微信敞开渠道,解析结果是没有unionid的。
        decryptResult = decrypt(sessionKey, encryptedIv, encryptedData);
    } catch (Exception e) {
        e.printStackTrace();
        return AjaxResult.error("微信登录失利!");
    }
    if (StringUtils.hasText(decryptResult)) {
        //假如解析成功,获取token
        String token = loginService.wxLogin(decryptResult);
        AjaxResult ajax = AjaxResult.success();
        ajax.put(Constants.TOKEN, token);
        return ajax;
    } else {
        return AjaxResult.error("微信登录失利!");
    }
}
/**
     * AES解密
     */
private String decrypt(String sessionKey,String encryptedIv,String encryptedData) throws Exception{
    // 转化为字节数组
    byte[] key = Base64.decode(sessionKey);
    byte[] iv = Base64.decode(encryptedIv);
    byte[] encData = Base64.decode(encryptedData);
    // 假如密钥缺乏16位,那么就补足
    int base =16;
    if (key.length % base !=0) {
        int groups = key.length / base +(key.length % base != 0 ? 1 : 0);
        byte[] temp = new byte[groups * base];
        Arrays.fill(temp,(byte) 0);
        System.arraycopy(key,0,temp,0,key.length);
        key = temp;
    }
    // 假如初始向量缺乏16位,也补足
    if (iv.length % base !=0) {
        int groups = iv.length / base +(iv.length % base != 0 ? 1 : 0);
        byte[] temp = new byte[groups * base];
        Arrays.fill(temp,(byte) 0);
        System.arraycopy(iv,0,temp,0,iv.length);
        iv = temp;
    }
    AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
    String resultStr = null;
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key,"AES");
        cipher.init(Cipher.DECRYPT_MODE,keySpec,ivSpec);
        resultStr = new String(cipher.doFinal(encData),"UTF-8");
    } catch (Exception e){
        //            logger.info("解析错误");
        e.printStackTrace();
    }
    // 解析加密后的字符串
    return resultStr;
}

3.6 SecurityConfig白名单放行Wxlogin接口

ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java

// 关于登录login 注册register 验证码captchaImage 微信登陆wxLogin 允许匿名拜访
.antMatchers("/login","/wxLogin", "/register", "/captchaImage").permitAll()

3.6 SysLoginService新增wxLogin办法

ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

/**
     * 微信登录
     *
     * @param decryptResult 登录凭据 只能用一次
     * @return
     */
public String wxLogin(String decryptResult){
    //字符串转json
    JSONObject jsonObject = JSONObject.parseObject(decryptResult);
    //        String unionid = jsonObject.getString("unionid");
    String openId = jsonObject.getString("openId");
    System.out.println("openId"+openId);
    //获取nickName
    String nickName = getStringRandom(16);// 生成16位随机昵称
    //获取头像
    //        String avatarUrl = jsonObject.getString("avatarUrl");
    String avatarUrl = "";
    //还可以获取其他信息
    //依据openid判别数据库中是否有该用户
    //依据openid查询用户信息
    SysUser wxUser = userMapper.selectWxUserByOpenId(openId);
    //假如查不到,则新增,查到了,则更新
    SysUser user = new SysUser();
    if (wxUser == null) {
        // 新增
        user.setUserName(getStringRandom(16));// 生成16位随机用户名
        user.setNickName(nickName);
        user.setAvatar(avatarUrl);
        //            wxUser.setUnionId(unionid);
        user.setOpenId(openId);
        user.setCreateTime(DateUtils.getNowDate());
        //新增 用户
        userMapper.insertUser(user);
    }else {
        //更新
        user = wxUser;
        user.setNickName(nickName);
        user.setAvatar(avatarUrl);
        user.setUpdateTime(DateUtils.getNowDate());
        userMapper.updateUser(user);
    }
    //组装token信息
    LoginUser loginUser = new LoginUser();
    loginUser.setOpenId(openId);
    //假如有的话设置
    loginUser.setUser(user);
    loginUser.setUserId(user.getUserId());
    // 生成token
    return tokenService.createToken(loginUser);
}
//生成随机用户名,数字和字母组成,
public static String getStringRandom(int length) {
    String val = "";
    Random random = new Random();
    //参数length,表明生成几位随机数
    for (int i = 0; i < length; i++) {
        String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
        //输出字母仍是数字
        if ("char".equalsIgnoreCase(charOrNum)) {
            //输出是大写字母仍是小写字母
            int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
            val += (char) (random.nextInt(26) + temp);
        } else if ("num".equalsIgnoreCase(charOrNum)) {
            val += String.valueOf(random.nextInt(10));
        }
    }
    return val;
}

4 演示效果

点击微信登陆后,向wxLogin()发起恳求,假如体系用户中有open_id,则回来token;如如该用户open_id,则添加该用户,用户名与昵称随机生成16位字符,调用登陆接口,回来token。

若依框架+uni-app实现微信小程序授权登录

若依框架+uni-app实现微信小程序授权登录