Step by Step之微信授权登录Java版

近期一个项目的微信小程序开发版本升级,原来的微信账号进行授权登录功能在微信升级后无法使用,看官方材料及百度发现没有一个相对完整实现,于是完成了基础功能后记录一下。


一、环境信息及实现基础思路

1、开发环境

项目开发环境信息:客户端-微信小程序;后端-Springboot;数据库-MySQL。

2、需求说明

小程序用户可无需登录,直接使用微信授权登录方式登录小程序,微信账号第一次授权登录会新增一个用户,用户名称和头像均为预先设定的默认值,登录成功后可自主修改用户名称和头像。

因较长时间里没有多端登录需求,所以可直接用微信小程序的openId,而无须记录unionId。

3、授权登录实现思路

微信账号授权登录分为两部分,第一部分是微信授权登录,具体分以下几步:一是微信小程序调用wx.login()获得临时登录凭证code,并发给后端;二是后端收到code后,与小程序的appId、appSecret一起,发送给微信接口服务器,并从微信接口服务返回session_key和openId;三是判断openId是否已经在用户表中已经存在,如存在表示该用户已经注册,直接返回加密后的token,如用户表中未找到该用户,则在用户表中插入该openId的新记录。

前后端及微信接口服务的交互时序图如下:

第二部分就是用户信息中昵称和用户头像信息的获取,项目上一个版本小程序使用getUserInfo获取用户信息,但微信升级后调整了授权流程,该函数已经无法获取用户的真实信息,只有一个默认头像和名称,目前只能通过用户信息修改页去引导用户主动填写头像和昵称。

二、Java后端实现

Step 1 数据库表调整

因上一个版本直接可获取用户昵称和头像信息,因此没有在用户表中存放openId,这次需要在用户表中新增一个字段。

alter table user_info add column open_id varchar(50) default null comment '微信openId';

Step 2 后端代码调整

Step 2.1 数据库字段调整相关

属于MVC开发过程中的常规调整,不再详述,一般需要在model中增加字段,在mapper.xml中增加基础字段等。

Step 2.2 新增微信授权登录API

在controller中增加新的映射/wxlogin,及对应的映射处理函数,核心逻辑为:

  • 从POST请求中读取json格式的code;
  • 通过application.yml配置文件读取微信服务器地址、appId、appSecret,并使用Hutool的HttpUtil去调用微信后端接口;
  • 根据获取的openId查询是否为已有用户,如果是进行登录相关操作并返回token;如果为新用户,则添加新用户信息,进行登录相关操作并返回token。

部分参考代码为:

@PostMapping("/wxlogin")
@Operation(summary = "微信code预登录(用于小程序登录)" , description = "微信小程序登录")
public ServerResponseEntity<LoginToken> wxLogin(@RequestBody String szBody) {
  JSONObject jsonBody = new JSONObject(szBody);
  String szCode = jsonBody.getStr("code");
  String appAuthUrl = authConfig.getAppAuthUrl().replace("{appid}", authConfig.getAppId()).replace("{appsecret}", authConfig.getAppSecret()).replace("{code}", szCode);
  // get the open_id and session_key from wx official site
  String szAuthResult = HttpUtil.get(appAuthUrl);
  JSONObject jsonAuthResult = new JSONObject(szAuthResult);
  String openId = jsonAuthResult.getStr("openid");
  String sessionKey = jsonAuthResult.getStr("session_key");
  // check the user using openid
  if (userService.count(new LambdaQueryWrapper<User>().eq(User::getOpenId, openId)) == 0) {
    // user is not registered, so register new user
    User newUser = new User();
    // code for setting fields of new user object
    userService.save(newUser);
    // code for user login
    return ServerResponseEntity.success(token);
  } else {
    // user with same openId exists, so login and return
    User user = getUser(openId);
    // code for user login
    return ServerResponseEntity.success(token);
  }
}

Step 2.3 用户信息修改接口

因用户昵称、头像的手工获取相关实现主要在微信小程序端,因此后端没有新增API,而是利旧使用用户信息修改API-/user/setUserInfo。

三、微信小程序实现

Step 3.1 授权登录相关

wx.login({
    success: res => {
        // 发送code到后台换取token
        var params = {
            url: "/wxlogin",
            method: "POST",
            data: {
                code: res.code
            },
            callBack: res => {
                // login success, save the token to storage
                wx.setStorageSync('token', res.accessToken);
                this.toHomePage();
            },
            errCallBack: res => {
                // not registered, move to register page
                console.log(res.msg)
                wx.showToast({
                    title: '授权登录失败,请注册用户',
                    icon: 'none'
                });
                this.toLoginPage();
            }
        };
        http.request(params);
    }
})      

Step 3.2 昵称头像信息获取相关

通过一个用户头像及昵称修改页面实现,第一段代码为userinfo.wxml

<view class="userinfo">
    <block>
        <image class="userinfo-avatar" src="{{avatarUrl}}" mode="cover"></image>
    </block>
    <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
        修改头像
    </button>
    <input type="nickname" value="{{nickName}}" class="nick-name-input" placeholder="请输入昵称" bindblur="changeNickName"/>
    <button class="login-btn" bindtap="userinfoModify">用户信息更新</button>
</view>

第二段代码为userinfo.js

Page({
    /**
     * 页面的初始数据
     */
    data: {
        avatarUrl: '',
        nickName: '',
    },

    onLoad() {
        this.getUserProfile()
    },

    // 获取用户信息
    getUserProfile() {
        // get the avatar and nickname of user
        var params = {
            url: "/user/userInfo",
            method: "GET",
            data: {},
            callBack: res => {
                // set the local variables of avatar and nickname
                this.setData({
                    nickName: res.nickName,
                    avatarUrl: res.pic == null ? 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0': 'https://img.<company-name>.cn/' + res.pic
                })
            },
            errCallBack: res => {
                // error while getting the avatar and nickname 
                wx.showToast({
                  title: '获取用户信息错误',
                })
            }
        };
        http.request(params);
    },

    // 用户选择头像
    onChooseAvatar(e) {
        this.setData({
            avatarUrl: e.detail.avatarUrl,
        })
    },

    // 用户修改昵称
    changeNickName(e) {
        let name = e.detail.value;
        if (name.length === 0) return;
        this.setData({
            nickName: e.detail.value
        })
    },

    // 修改用户昵称、用户名等
    userinfoModify() {
        let uploadUrl = '';
        var that = this;

        // check the value of avatar and nickname
        if (that.data.avatarUrl === '' || that.data.nickName === '') {
            // value of field is empty 
            wx.showToast({
              title: '用户信息不全',
            });
            return;
        }
        // upload the avatar to the server side 
        var avatar_params = {
            url: "/file/upload",
            filePath: that.data.avatarUrl,
            method: "POST",
            data: {},
            callBack: function(res) {
                // upload success
                uploadUrl = res.data;
                console.log('nickName:', that.data.nickName);
                var params = {
                    url: "/user/setUserInfo",
                    method: "PUT",
                    data: {
                        avatarUrl: uploadUrl,
                        nickName: that.data.nickName
                    },
                    callBack: function(resUpdate) {
                        // update success
                        wx.showToast({
                            title: '用户信息更新成功',
                        })
                    },
                    errCallBack: function(resUpdate) {
                        // update failure
                        console.log(resUpdate);
                        wx.showToast({
                            title: '用户信息更新失败',
                        })
                    }
                };
                http.request(params);        
            },
            errCallback: function(res) {
                console.log(res);
                wx.showToast({
                    title: '头像上传失败',
                })
            }
        }
        http.uploadFile(avatar_params);
    },
举报
评论 0