基于若依框架扩展微信扫码登录功能

2024-10-07 19:53:26

若依框架前后端分离版基础上扩展微信扫码登录功能

# 前言

最近在学习java相关知识,朋友推荐看一下若依框架,于是在若依框架的基础上扩展了一下微信登录功能。

# 实现思路

标准的扫码登录需要申请微信开放平台账号,并拥有一个已审核通过的网站应用,即:网站应用微信登录,流程比较麻烦。这里为了省事就基于微信公众号网页授权来进行登录操作,同样也可以学习一下扫码登录的流程。
基于公众号网页授权进行扫码登录我们只需要准备一个公众号(需要认证过的服务号或者直接申请测试公众号)。
因为若依框架已经自带了用户表,所以我们这里的扫码登录要拆分成两部分来开发:

  • 若依用户微信绑定
  • 若依用户微信扫码登录

# 实现过程

# 微信绑定开发

微信绑定的场景是我们需要先登录若依系统,然后打开个人信息页面,在页面上面点击微信绑定按钮弹出二维码,然后手机微信扫码完成绑定。

绑定部分的扫码流程大致如下:

PC 端点击微信绑定按钮后从当前登录token获取到用户的key,基于用户的key拼接链接在前端弹出一个二维码,二维码地址(附带了生成的用户key)是网页授权链接,微信扫码打开后直接进行公众号网页授权,获取 code 后重定向给后端接口来拿到用户 openid 更新到用户表中完成绑定。

基于此流程结合若依框架我们需要开发如下几个接口并修改页面:

  • 微信openid绑定
  • 用户uuid查询绑定状态及openid
  • 个人信息页追加微信绑定功能

因为需要存取用户微信信息,所以修改代码之前我们需要先向 sys_user 用户表追加两个字段:wx_nick_name、openid。

# 微信openid绑定开发
  1. ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/下面新建WxController.java用于处理微信相关请求。然后在WxController中追加微信openid绑定方法:
/**
 * 获取openid
 */
@GetMapping("/bind-openid")
public AjaxResult getOpenid(@RequestParam("code") String code, @RequestParam("key") String key) throws IOException
{
    AjaxResult ajax = AjaxResult.success();
    SysUser u = userService.getOpenid(code);
    String openid = u.getOpenid();
    LoginUser userCache = redisCache.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + key);
    SysUser user = new SysUser();
    user.setUserId(userCache.getUserId());
    user.setOpenid(openid);
    user.setWxNickName(u.getWxNickName());
    userService.updateUserOpenid(user);

    ajax.put("openid", openid);
    ajax.put("wxnickname", u.getWxNickName());
    return ajax;
}

方法里主要完成了微信公众号授权code的接收,并请求微信接口获取到用户openid,从当前用户登录信息redis缓存中拿到用户id然后把微信用户openid更新到用户表完成绑定。
这里为了方便,直接把code重定向给到了接口地址。

  1. ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java中追加微信code获取用户openid方法:
/**
 * 通过code获取用户openid
 *
 * @param code 微信公众号网页授权码
 * @return 用户openid
 */
@Override
public SysUser getOpenid(String code) {
    RestTemplate restTemplate = new RestTemplate();
    JSONObject jsonData = null;

    // 构建获取access_token的URL
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?"
            + "appid=" + appId
            + "&secret=" + secret
            + "&code=" + code
            + "&grant_type=authorization_code";

    System.out.println("url: " + url);
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);

    System.out.println("responseEntity: " + responseEntity);
    if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
        jsonData = JSONObject.parseObject(responseEntity.getBody());
    }

    String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?lang=zh_CN"
            + "&access_token=" + jsonData.getString("access_token")
            + "&openid=" + jsonData.getString("openid");

    ResponseEntity<String> responseUserEntity = restTemplate.getForEntity(userInfoUrl, String.class);
    if (responseUserEntity.getStatusCodeValue() == 200 && responseUserEntity.getBody() != null) {
        JSONObject jsonUserData = JSONObject.parseObject(new String(responseUserEntity.getBody().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
        System.out.println("jsonUserData: " + jsonUserData);
        SysUser user = new SysUser();
        user.setOpenid(jsonUserData.getString("openid"));
        user.setWxNickName(jsonUserData.getString("nickname"));
        return user;
    }

    return null;
}

因为这里用到了公众号appid和secret,所以我们要在 ruoyi-admin/src/main/resources/application.yml 配置文件中追加相应配置:

# 公众号配置
wechat:
  # 应用ID
  appid:
  # 应用密钥
  secret:

追加配置之后需要在 SysUserServiceImpl 中补充对应私有属性:

@Value("${wechat.appid}")
private String appId;

@Value("${wechat.secret}")
private String secret;
  1. ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java中追加修改用户openid的方法:
/**
 * 修改用户openid
 *
 * @param user 用户信息
 * @return 结果
 */
@Override
public int updateUserOpenid(SysUser user)
{
    return userMapper.updateUser(user);
}

这里需要我们在 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java 中追加对应的方法声明:

/**
 * 修改用户信息
 * 
 * @param user 用户信息
 * @return 结果
 */
public int updateUser(SysUser user);

并且在 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 中补充更新对应的sql,即向更新用户sql的set部分追加微信openid及昵称的更新判断:

<update id="updateUser" parameterType="SysUser">
 	...
	<if test="openid != null and openid != ''">openid = #{openid},</if>
	<if test="wxNickName != null and wxNickName != ''">wx_nick_name = #{wxNickName},</if>
	...
</update>
  1. 因为新增了微信openid及昵称的信息,所以需要修改一下ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java文件,追加微信昵称及openid属性:
/** 用户微信昵称 */
@Excel(name = "用户微信昵称")
private String wxNickName;

/** openid */
@Excel(name = "openid")
private String openid;

public String getOpenid()
{
    return openid;
}

public void setOpenid(String openid)
{
    this.openid = openid;
}

public String getWxNickName()
{
    return wxNickName;
}

public void setWxNickName(String wxNickName)
{
    this.wxNickName = wxNickName;
}

这样我们就完成了微信openid绑定接口的开发。

# 用户uuid查询绑定状态及openid

下面就是要查询微信绑定的信息了。

  1. ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/WxController.java中追加户uuid查询openid方法:
/**
 * 已登录用户uuid查询openid
 */
@GetMapping("/uuid")
public AjaxResult getCode(@RequestParam("uuid") String uuid) throws IOException
{
    AjaxResult ajax = AjaxResult.success();
    LoginUser userLogin = redisCache.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + uuid);
    SysUser user = userService.selectUserById(userLogin.getUserId());
    System.out.println("user-openid: " + user.getOpenid());
    System.out.println("user-wxnickname: " + user.getWxNickName());
    if (user.getOpenid() != null) {
        ajax.put("openid", user.getOpenid());
        ajax.put("wxnickname", user.getWxNickName());
    }
    ajax.put("status", user.getOpenid() != null ? 1: 0);
    return ajax;
}
  1. ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml的selectUserVo查询sql中追加openid、微信昵称查询:
<sql id="selectUserVo">
select  ...,
        u.openid, u.wx_nick_name,
        ...
        from sys_user u
</select>

这样查询到的用户信息里面就会包含绑定的微信信息了。

# 接口访问权限修改

因为扫码绑定的时候用户在手机上是没有登录的所有要在 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java 中把绑定接口加入到允许列表中:

requests.antMatchers("/login", "/register", "/captchaImage", "/bind-openid").permitAll()

到这里绑定需要开发的接口就修改完了,我们接着来修改页面。

# 个人信息页追加微信绑定
  1. 前端项目依赖追加。因为扫码需要生成二维码,所以这里为了方便使用前端插件vue-qr生成,在前端项目根目录npm install vue-qr即可。

  2. 追加接口。在ruoyi-ui/src/api/system/user.js中追加我们上面写好的查询绑定状态接口:

export function getUUid(data) {
    return request({
        url: '/uuid?uuid=' + data.uuid,
        method: 'get'
    })
}
  1. 页面修改。修改个人信息页ruoyi-ui/src/views/system/user/profile/userInfo.vue

3.1 增加微信绑定按钮及弹窗:

<el-form-item label="微信">
    <span class="mr10">{{form.wxnickname || '未绑定'}}</span>
    <el-button v-if="form.openid == '' || form.openid == null" :loading="bindloading" size="medium" type="primary" class="btn" @click.stop="wxBind()">绑 定</el-button>
    <el-button v-else :loading="bindloading" size="medium" type="primary" class="btn" @click.stop="unBind()">解
        绑</el-button>
</el-form-item>

<el-dialog title="绑定微信" custom-class="bind-dialog" class="new-common-dialog" :visible.sync="bindWxVisible" :close-on-click-modal="false" :close-on-press-escape="false" @close="wxLoginClose" width="320px">
    <div class="qr-code">
        <vue-qr :text="qrUrl" :size="280"></vue-qr>
    </div>
</el-dialog>

3.2 导入依赖接口及相关方法:

import {
    updateUserProfile,
    getUUid
} from "@/api/system/user";
import {
    getToken
} from '@/utils/auth'
import vueQr from 'vue-qr'

3.3 引用二维码组件:

components: {
    vueQr,
},

3.4 data中新增属性:

timer: null,
bindWxVisible: false,
bindloading: false,
qrUrl: '',

3.5 修改user监听方法,使页面能够回显微信昵称:

user: {
    handler(user) {
        console.log('user', this.user)
        if (user) {
            this.form = {
                nickName: user.nickName,
                wxnickname: user.wxNickName,
                phonenumber: user.phonenumber,
                email: user.email,
                sex: user.sex,
                openid: user.openid
            };
        }
    },
    immediate: true
}

3.6 增加微信绑定相关方法:

 wxBind() {
    let token = getToken()
    const key = JSON.parse(atob(token.split('.')[1]))['login_user_key'];
    const redirect_uri = `http://web-hub.uat.kai12.cn/auth.html?backUrl=${本机IP}:8080/bind-openid?key=${key}`
    const codeUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号appid&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&scope=snsapi_userinfo&state=123456#wechat_redirect`
    this.qrUrl = codeUrl
    console.log(this.qrUrl)
    this.bindWxVisible = true
    let that = this
    this.timer = setInterval(function() {
        getUUid({
                uuid: key
            })
            .then((res) => {
                console.log(res)
                if (res.status === 1) {
                    that.bindWxVisible = false
                    that.form.openid = res.openid
                    that.form.wxnickname = res.wxnickname
                    that.$message({
                        type: 'success',
                        message: '操作成功',
                    })
                    clearTimeout(this.timer)
                }
            })
            .catch((err) => {
                clearTimeout(that.timer)
            })
    }, 1000)
},
wxLoginClose() {
    this.timer && clearTimeout(this.timer)
},

这里主要是通过获取浏览器缓存的token,从中解析出若依用户的 login_user_key 来实现关联绑定的。

然后整个微信绑定流程就大致完成了。

# 微信扫码登录

实现思路

PC 端点击微信登录时生成一个 uuid 存入 redis 并弹出一个二维码,二维码地址(附带了生成的 uuid)是移动端的网页,微信扫码后打开的是配置好的网页授权链接,通过网页授权的方式获取 code 拿到用户 openid 后存入redis中,PC 端通过轮询方式根据生成的 uuid 查询用户 openid 进行登录。

基于以上实现思路结合若依框架需要开发如下几个接口并修改页面:

  • uuid 生成
  • uuid 绑定 openid
  • openid/uuid 登录
  • 登录页追加扫码登录功能
# uuid 生成

ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/WxController.java 中追加uuid 生成方法:

/**
 * 扫码登录用uuid生成
 */
@GetMapping("/uuid/get")
public AjaxResult getUUID() throws IOException
{
    AjaxResult ajax = AjaxResult.success();
    String uuid = IdUtils.simpleUUID();
    String verifyKey = CacheConstants.WX_OPENID_KEY + uuid;
    redisCache.setCacheObject(verifyKey, null, 1, TimeUnit.MINUTES);
    ajax.put("uuid", uuid);
    return ajax;
}

方法里主要利用若依自带的方法生成UUID并存储到redis里(有效期设置为1分钟),需要在 ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java 中增加一个 WX_OPENID_KEY 常量用于存放openid:

/**
 * 微信openid redis key
 */
public static final String WX_OPENID_KEY = "wx_openid:";
# uuid 绑定用户openid

同样的,在 WxController.java 中追加uuid绑定用户openid的方法:

/**
 * uuid绑定openid
 */
@GetMapping("/uuid/bind/openid")
public AjaxResult bindOpenid(@RequestParam("code") String code, @RequestParam("uuid") String uuid) throws IOException
{
    AjaxResult ajax = AjaxResult.success();
    SysUser user = userService.getOpenid(code);
    String openid = user.getOpenid();
    String wxNickName = user.getWxNickName();
    String verifyKey = CacheConstants.WX_OPENID_KEY + uuid;
    long expire = redisCache.getExpire(verifyKey);
    redisCache.setCacheObject(verifyKey, openid);
    if (expire > 0) {
        redisCache.expire(verifyKey, expire, TimeUnit.SECONDS);
    }
    ajax.put("openid", openid);
    ajax.put("wxNickName", wxNickName);
    return ajax;
}

该方法用于微信扫码时接收微信重定向过来的code以及点击扫码登录时产生的随机UUID,调用上一篇中 SysUserServiceImpl 追加的 getOpenid 方法获取到用户的openid和微信昵称,并把openid更新到对应UUID的redis缓存中。

# uuid 登录

WxController.java 中继续追加UUID登录方法:

/**
 * uuid登录
 */
@GetMapping("/uuid/login")
public AjaxResult loginByOpenId(@RequestParam("uuid") String uuid) throws IOException
{
    AjaxResult ajax = AjaxResult.success();
    String verifyKey = CacheConstants.WX_OPENID_KEY + uuid;
    String openid = redisCache.getCacheObject(verifyKey);
    ajax.put("status", 0);
    System.out.println("openid:{}" + openid);
    if(openid != null) {
        SysUser user = userService.selectUserByOpenId(openid);
        System.out.println("用户:{}" + user);
        if (user == null)
        {
            System.out.println("用户不存在");
            return error("用户不存在");
        }
        LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
        // 生成token
        String token = tokenService.createToken(loginUser);
        ajax.put("token", token);
        ajax.put("status", 1);
        redisCache.deleteObject(verifyKey);
    }

    return ajax;
}

该方法用于在前端轮询随机生成的UUID,如果在缓存中查询到 openid 就使用 openid 查询用户信息,所以需要在 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java 中补充通过openid查询用户的方法:

/**
 * 通过openid查询用户
 *
 * @param openId 用户名
 * @return 用户对象信息
 */
@Override
public SysUser selectUserByOpenId(String openId)
{
    return userMapper.selectUserByOpenId(openId);
}

同时要在ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java中追加对应的方法声明:

/**
 * 通过openId查询用户
 *
 * @param openId openid
 * @return 用户对象信息
 */
public SysUser selectUserByOpenId(String openId);

并且在 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 中补充对应的查询sql,根据微信openid获取用户:

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

如果能查询到用户就校验一下用户的权限并生成登录token一起返回给前端。

# 接口访问权限修改

扫码登录的接口都是在没有登录的时候调用的,所以要在 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java 中把相关接口加入到允许列表中:

requests.antMatchers("/login", "/register", "/captchaImage", "/bind-openid", "/uuid/get", "/uuid/login", "/uuid/bind/openid").permitAll()

到这里我们就完成了微信扫码登录所需接口的开发,我们接着来修改页面。

# 登录页追加微信扫码登录
  1. 追加接口。在ruoyi-ui/src/api/system/user.js中追加我们上面写好的接口:
// 查询登录用uuid
export function getLoginUUid() {
    return request({
        url: '/uuid/get',
        method: 'get'
    })
}

// uuid登录
export function uuidLogin(data) {
    return request({
        url: '/uuid/login?uuid=' + data.uuid,
        method: 'get'
    })
}
  1. 页面修改。修改登录页ruoyi-ui/src/views/login.vue

2.1增加微信登录按钮及弹窗:

<div class="text-center">
    <img class="mt10 wechat-icon" src="@/assets/images/wx-login.png" alt="微信登录" @click.stop="wxlogin()">
</div>

<el-dialog title="扫码登录" custom-class="bind-dialog" class="new-common-dialog" :visible.sync="bindWxVisible" :close-on-click-modal="false" :close-on-press-escape="false" @close="wxLoginClose" width="320px">
    <div class="qr-code">
        <vue-qr :text="qrUrl" :size="280"></vue-qr>
        <div v-if="bindTimeout" class="tip text-center">
            二维码已失效,请点击
            <i class="el-icon-refresh" @click="wxlogin"></i> 刷新
        </div>
    </div>
</el-dialog>

2.2导入依赖接口及相关方法:

import {
    getToken
} from "@/utils/auth";
import {
    getLoginUUid,
    getUUid,
    uuidLogin
} from "@/api/system/user";
import vueQr from 'vue-qr'

2.3引用二维码组件:

components: {
    vueQr,
},

2.4data中新增属性:

timer: null,
    bindWxVisible: false,
    bindTimeout: false,
    qrUrl: '',

2.5 ruoyi-ui/src/store/modules/user.js 文件增加登录缓存方法:

// uuid登录
uuidLogin({
    commit
}, userInfo) {
    setToken(userInfo.token)
    commit('SET_TOKEN', userInfo.token)
},

2.6登录页增加微信登录相关方法:

 wxlogin() {
    getLoginUUid().then(response => {
        console.log(response.uuid)
        const uuid = response.uuid
        const redirect_uri = `http://web-hub.uat.kai12.cn/auth.html?backUrl=${本机IP}:8080/uuid/bind/openid?uuid=${uuid}`
        const codeUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号appid&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&scope=snsapi_userinfo&state=123456#wechat_redirect`
        this.qrUrl = codeUrl
        console.log(this.qrUrl)
        this.bindWxVisible = true
        let that = this
        let counter = 1
        this.timer && clearTimeout(this.timer)
        this.timer = setInterval(function() {
            uuidLogin({
                    uuid: uuid
                })
                .then((res) => {
                    console.log(res)
                    counter++
                    console.log(counter)
                    if (counter === 60) {
                        clearTimeout(that.timer)
                        that.bindTimeout = true
                    }
                    if (res.status === 1) {
                        clearTimeout(that.timer)
                        that.bindWxVisible = false
                        that.$message({
                            type: 'success',
                            message: '登录成功',
                        })
                        clearTimeout(that.timer)
                        that.$store.dispatch("uuidLogin", res)
                        setTimeout(() => {
                            that.$router.push({
                                path: that.redirect || "/"
                            }).catch(() => {});
                        }, 1500)
                    }
                })
                .catch((err) => {
                    that.bindWxVisible = false
                    clearTimeout(that.timer)
                })
        }, 1000)
    });
},
wxLoginClose() {
    this.timer && clearTimeout(this.timer)
},

这里主要是通过轮询随机生成的uuid后,手机扫码将code以重定向的方式给到后端的 uuid/bind/openid 接口同时完成UUID传参来实现登录。

然后微信扫码登录部分我们就完成了。

# 参考资料

本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 许可协议。可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

扫描下方二维码阅读当前文章

浏览器、微信扫码

评 论:

好文推荐
每天进步一点点~