⛰️个人主页:     蒾酒

🔥系列专栏:《spring boot实战》


目录

写在前面

登录流程

流程解析

具体实现

相关代码

说明

服务端

小程序端

写在最后


 

写在前面

本文介绍了springboot开发微信小程序后端服务中,用户登录功能的设计与实现,坚持看完相信对你有帮助。

同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。

 

登录流程

如图:

这是微信官方文档中微信小程序登录的流程时序图,我在图中红色序号标注的五步就是完整的微信小程序登录流程。

 

流程解析

  1. 小程序端通过wx.login()获取用户登录凭证code。
  2. 小程序将code发送到服务端的登录接口。
  3. 服务端的登录接口使用该code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。
  4. 服务端拿到openid后,可以根据业务需求生成用户令牌(Token),通常包括用户信息、权限等,并返回给小程序。
  5. 小程序在本地缓存该用户令牌。
  6. 后续小程序发起请求时,在请求头中携带该用户令牌(Token)。
  7. 服务端接收到请求时,验证用户令牌的有效性,确保用户是经过认证和授权的。

 

具体实现

登录接口设计思路

  1. 接收小程序端传递的code参数。
  2. 使用code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。
  3. 在数据库用户表中查询是否存在该openid。
  4. 如果存在该openid,则使用查询到的用户信息生成令牌(Token)。
  5. 如果不存在该openid,则进行用户注册操作,将新用户信息插入数据库用户表,并生成令牌(Token)。
  6. 返回生成的令牌(Token)给小程序端。

相关代码

说明

下面代码基于jdk17,发请求用的时jdk自带的http工具类,如果你的jdk版本低于11,可以使用okhttp依赖来发请求,然后jwt相关工具类代码在我本专栏的其他文章里面,用到的ORM框架为mybatis-plus整合相关代码也在本专栏。

服务端

登录dto

@Data
public class UserLoginDTO {
    @NotBlank(message = "code不能为空")
    private String code;

    private Map<String,Object> userInfo;//用户完善信息

}

登录返回vo

@Data
@Builder
public class UserLoginVO implements Serializable {
    private Long id; //用户id

    private String openid;//用户在小程序唯一标识

    private String token;//用户登录凭证
   
}

获取openid方法

使用的是jdk17自带的HttpClient,也可以使用okhttp,或者糊涂工具包里面的。

 private String getOpenid(String code) {
        HttpClient httpClient = HttpClient.newHttpClient();
        // 构建请求参数字符串
        String params = String.format("appid=%s&secret=%s&js_code=%s&grant_type=%s",
                URLEncoder.encode(wxMiniConfig.getAppId(), StandardCharsets.UTF_8),
                URLEncoder.encode(wxMiniConfig.getAppSecret(), StandardCharsets.UTF_8),
                URLEncoder.encode(code, StandardCharsets.UTF_8),
                URLEncoder.encode("authorization_code", StandardCharsets.UTF_8));

        // 创建 GET 请求
        String url = "https://api.weixin.qq.com/sns/jscode2session?" + params;
        HttpRequest httpRequest = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .build();

        try {
            // 发送 GET 请求
            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

            // 获取响应结果
            int statusCode = httpResponse.statusCode();
            String responseBody = httpResponse.body();
            JSONObject responseJson = JSONObject.parseObject(responseBody);
            String openid = responseJson.getString("openid");

            // 处理响应结果
            System.out.println("状态代码: " + statusCode);
            System.out.println("响应正文: " + responseBody);
            return  openid;
        } catch (Exception e) {
           log.error("发送请求时出错: {}", e.getMessage());
           return null;
        }
    }

 

登录逻辑代码

在判断出是新用户时,进行注册操作还需要将请求携带过来的userInfo完善信息,用户名,头像等一同封装进User对象再插入到数据库这里我就省略了

    @Override
    public UserLoginVO login(UserLoginDTO userLoginDTO) {
        //获取openid
        String openid = getOpenid(userLoginDTO.getCode());
        if(StringUtils.isBlank(openid)){
            throw new GeneralBusinessException(ResultEnum.USER_OPENID_ERROR); // openid获取失败
        }

        User user = new LambdaQueryChainWrapper<>(userMapper).eq(User::getOpenid, openid).one();

        if(Objects.isNull(user)){
            // 用户不存在则注册
            user = new User();
            //获取完善信息,用户呢称、头像url
            userLoginDTO.getUserInfo
            //封装信息。。。。。。
            //。。。。。。。。。。
            
            user.setOpenid(openid);

            // 注册用户
            if(!this.save(user)){
                throw new GeneralBusinessException(ResultEnum.USER_REGISTER_FAIL); // 用户注册失败
            }
        }

        return UserLoginVO.builder()
                .id(user.getUserId())
                .openid(user.getOpenid())
                .token(jwtUtils.generateToken(Map.of("userId", user.getUserId()), "user"))
                .build();
    }

 

小程序端

如图点击登录按钮会进行登录(注册)

wxml:

 <button bindtap="handleLogin">登录</button>

js:

// index.js
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'

Page({
  data: {
    userInfo: {
      avatarUrl: defaultAvatarUrl,
      nickName: null,
    },
    hasUserInfo: false,
    canIUseGetUserProfile: wx.canIUse('getUserProfile'),
    canIUseNicknameComp: wx.canIUse('input.type.nickname'),
  },
  bindViewTap() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },
  onChooseAvatar(e) {
    const {
      avatarUrl
    } = e.detail
    const {
      nickName
    } = this.data.userInfo
    this.setData({
      "userInfo.avatarUrl": avatarUrl,
      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
    })
  },
  onInputChange(e) {
    const nickName = e.detail.value
    const {
      avatarUrl
    } = this.data.userInfo
    this.setData({
      "userInfo.nickName": nickName,
      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
    })
  },
  // 点击登录按钮时触发的方法
  handleLogin: function () {
    wx.login({
      success: res => {
        const code = res.code;
        if (code) {
          //判断用户是否已经填写头像和昵称
          if (this.data.userInfo.avatarUrl === defaultAvatarUrl || this.data.userInfo.nickName === null) {
            wx.showToast({
              title: '填写头像和昵称',
              icon: 'none',
              duration: 1000
            });
            return
          }
          // 将 code 和用户信息发送到后端服务器进行登录验证
          wx.request({
            url: 'http://localhost:8080/user/login',
            method: 'POST',
            data: {
              code: code,
              userInfo: this.data.userInfo
            },
            success: res => {
              const {
                token
              } = res.data; // 假设后端返回包含 token 的数据
              if (token) {
                // 登录成功,保存 token到本地存储
                wx.setStorageSync('token', token);
                // 跳转到主页或其他页面
                wx.navigateTo({
                  url: '/pages/home/home'
                });
              } else {
                // 登录失败,显示提示信息
                wx.showToast({
                  title: '登录失败,请重试',
                  icon: 'none'
                });
              }
            },
            fail: err => {
              console.error('获取用户信息失败', err);
            }
          });
        } else {
          console.error('获取登录凭证失败', res.errMsg);
        }
      },
      fail: err => {
        console.error('调用登录接口失败', err);
      }
    });
  }
})

补充:实际开发会把wx.request进行封装后在使用,这里作为演示我就直接使用了,实际发送的post请求,请求体会携带code,和用户昵称、头像路径等信息。

写在最后

本文完整的介绍了springboot开发微信小程序服务端中用户登录功能的设计思路,希望对你有帮助。

 

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐