普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月29日首页

基于uniapp+nodejs实现小程序登录功能

2025年6月29日 19:20

本系列教程,以【中二少年工具箱】小程序为案例demo,具体效果请在微信中搜索该小程序查看。或在微信输入框输入 【#小程序://中二少年工具箱/6buitXgPnjHV21r】


一、概述

1.1 技术选型:

小程序端:uniapp

后端:nodejs+midwayjs+typeorm

数据库:mysql

1.2 登录功能实现方案:

1.小程序端调用接口uni.login获取随机code

2.将随机code传递给后端接口,后端接口中调用小程序官方api,获取用户信息

3.后端将用户信息保存到数据库中用户信息表,并将保存结果返回给前端

4.前端缓存用户信息,并显示

二、小程序端实现

代码实现:

 function getUserInfoByWx() {
        isLoad.value = true

        uni.login({
            provider: 'weixin', //使用微信登录
            success: function (loginRes) {
                const userData = {
                    code: loginRes.code
                }
                // console.log('userData',userData);
                getUserInfoByWxApi(userData).then(res => {
                    console.log(res)
                    if (res.success) {
                        userInfoStore.setUserInfo({
                            userName: res.data.userName,
                            openidWx: res.data.openidWx
                        })
                    } else {
                        openMessage({
                            text: '自动创建用户出错,请点击登录手动创建'
                        })
                    }
                }).catch(err => {
                    console.log('eeeeeeeeeeeeeee', err)
                    openMessage({
                        text: '登录失败,请联系开发者'
                    })
                })
                    .finally(() => {
                        // debugger
                        isLoad.value = false
uni.$emit('loginFinish');
                    })
            },
            fail() {
                isLoad.value = false
            }
        });
    }

代码解释:

1.isLoad:前端是否显示正在登录的动画。

2.uni.login:uniapp提供的登录api,可以生成各平台的临时code。

3.getUserInfoByWxApi:调用后端接口,将临时code作为参数传递给后端,后端再调用官方接口完成登录。

4.userInfoStore.setUserInfo登录成功后,在全局状态管理中保存用户信息

上面的代码,大部分都是和前端登录相关的业务代码,真正核心的是生成了临时code并传递给后端,因为调用官方接口只能在后端代码中运行。

三、后端实现

后端代码实现可分为两步,一是调用官方接口,获取小程序官方返回的用户信息;二是根据业务需求,将用户信息保存到我们的数据库中。

controller层代码实现;

  @Post('/getUserInfoByWx')
  async getUserInfoByWx(@Body() userData: { code: string }) {
    const openidRs = await this.loginService.getOpenidWx(userData)
    const openidKey = 'openidWx'
    const rs = await this.loginService.getUserInfoByPlat(openidRs, openidKey)
    return rs
  }

上面代码的code就是小程序端传入的临时code,主要用于getOpenidWx方法中,获取调用官方接口后的返回结果。

3.1 调用官方接口

上面代码中的getOpenidWx方法即是调用官方接口:

 const openidRs = await this.loginService.getOpenidWx(userData)

具体的service实现:

  /*根据临时code,获取wx返回的登录信息*/
  async getOpenidWx(userData:{code:string} | any): Promise<any> {
    const url = 'https://api.weixin.qq.com/sns/jscode2session';
    const data = {
      appid: 'wx9cxxxxxxxxxxxxx',
      secret: '66bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      js_code: userData.code,
      grant_type: 'authorization_code'
    }
    const result = await this.httpService.get(url, {params: data})
    return result
  }

官方的文档如下:

在这里插入图片描述 结合文档和我写的接口示例,我为大家总结了关键点:

1.接口通过GET方式访问

2.参数包括appid、secret、js_code、grant_type都是通过url参数的方式传递。

3.appid+secret:开发者的身份认证,通过开发管理平台获取:

在这里插入图片描述

4.js_code:小程序端传递来的临时code

5.grant_type:照官网写,别问为什么。

返回结果中我们需要重点关注的参数是openid,这是每一个用户的唯一标识。

3.2 保存用户信息到数据库

上面controller层一共调用了两个方法,一个是上面的调用官方接口,另一个就是保存用户信息到数据库并返回用户信息:

  const rs = await this.loginService.getUserInfoByPlat(openidRs, openidKey)

上面是兼容各平台的写法,我们可以忽略openidKey参数,只以微信小程序为例,具体的service实现为:

/**
   * 根据各平台的openid获取用户信息,可用于首次登录时,自动注册*/
  async getUserInfoByPlat(openidRs,openidKey:string){
    // debugger
    // 通过三方平台的
    let rs:any={}
    let findWxUserRs = new WxUser()
    if (openidRs.status == 200 && (openidRs.data.openid || openidRs.data.data.openid)) {
      const userInfo={}
      userInfo[openidKey]=openidRs.data.openid || openidRs.data.data.openid
      findWxUserRs = await this.wxUserService.getWxUserByUserInfo(userInfo) || new WxUser()
    }

    if (findWxUserRs && findWxUserRs.id) {
      //用户已注册,获取用户信息
      if(!findWxUserRs.userExtraEntity){
        // 兼容旧数据,若没有extra信息,则创建
        rs = await this.wxUserService.saveWxUser(findWxUserRs)
      }else{
        rs=findWxUserRs
      }
    } else {
      //用户未注册,则保存并登录
      // /*TODO:tt和wxopenid的层级不同,需要改造*/
      findWxUserRs[openidKey] = openidRs.data.openid || openidRs.data.data.openid
      rs = await this.wxUserService.saveWxUser(findWxUserRs)
    }
    return rs
  }

代码解释:

1.openidRs是调用官方接口的方法返回的用户信息,如果它的status为200,并且openid有值,则说明调用官方接口成功。判断里之所以有两种层级,可能是因为某个平台的返回结果比较奇葩,代码过于久远,我也记不清了。

2.findWxUserRs是以openid作为筛选条件,筛选数据库的用户表,第一版代码不用像我写这么麻烦,我的openid可能分为openidWX,openidTT等等。第一次做这个功能,就在用户表里增加字段openid即可,然后根据这个字段筛选用户表。

3.如果以openid为筛选条件查到了用户信息,说明用户已注册,返回查询到的数据库中的用户信息。不用关心我的userExtraEntity对象判断,我的用户表结构发生过变化,为了兼容旧数据,这里做了判断。

4.如果以openid为筛选条件未查询到信息,则说明用户未注册,应该主动注册用户。注册用户的逻辑就因人而异了,我生成了随机的用户名称和用户id,再加上openid字段,保存了基本的用户信息。

5.保存成功后,返回用户信息。

我们要理清楚一个概念:用户信息。

通过官方接口获取的用户信息,是小程序官方提供返回结果,现阶段对我们最重要的是openid。

通过我们自己业务代码获取的用户信息,是返回数据库中的用户表信息。现阶段最重要的是用户名(userName)+id(表id,在我们业务中的唯一标识)+openid(用户在某小程序中的唯一标识)。

为什么不能用openid代替id成为我们业务表中的唯一标识,因为以后有可能还会集成其他小程序平台,openid在某小程序的各个场景中是唯一标识,但对于我们系统而言,它只是一个业务字段。

三、后端返回结果,前端显示

前端调用接口最终返回的结果,是保存在数据表中的用户信息:用户名+id+openid。

在上面前端代码中,成功调用接口后,主要做了两个操作:

      userInfoStore.setUserInfo({
                            userName: res.data.userName,
                            openidWx: res.data.openidWx
                        })

...省略代码
uni.$emit('loginFinish');

代码解释:

1.userInfoStore.setUserInfo:是维护全局状态管理中的用户信息,并且使用pinia做成响应式,只要改变,小程序端的页面就会显示对应用户名。

在这里插入图片描述

2.uni.$emit('loginFinish'):是一个事件通知机制。当登录模块所有操作完成,再触发loginFinish。其他组件中需要等待登录操作完成后才能执行的代码,需要严格控制执行顺序的代码,就在合适的位置插入uni.$on()监听。


总结

博主的大部分demo示例都会放到:中二少年学编程的示例项目。戳链接,查看示例效果。如果链接失效,请手动输入地址:lizetoolbox.top:8080/#/

本文知识点总结:

1.uni.login获取登录随机code,传递给后端接口。

2.后端代码中,调用官方接口获取平台返回的openid

3.将openid更新到数据库的用户信息表,如果没有该用户,则创建。

4.后端接口返回用户信息,在前端显示并执行后续操作

有任何前端项目、demo、教程需求,都可以联系博主,博主会视精力更新,免费的羊毛,不薅白不薅!~

❌
❌