手把手云开发小程序-(三)_uniclould小程序的登录

一,微信程序的登录流程概述

其实uniclould有uni-id来实现登录的一系列逻辑,但是它太多对我而言无用的数据了.我只需要简单的数据.那就自己写呗.

微信官网已经把登录的流程写得很清楚了,官网地址:小程序登录 | 微信开放文档 (qq.com)

首先小程序通过wx.login()获取登录凭证code,每次调用code均不同,有效时间是5分钟,该code被微信接口服务验证一次就会失效了,小程序获取到code之后,通过wx.request()将code发送到开发者服务器,开发者服务器将appid,appSecret(密钥),和code发送给微信接口服务去校验登录凭证,成功会返回session_key(会话信息记录)和openid(用户唯一标识),用户登录成功后,开发者服务器可以将openid和session_key保存,生成一个自定义登录态的token(令牌)响应回去给小程序,通过token可以查询openid和session_key,小程序下次请求只要携带着token就可以证明已经登录。

接下来,我会照着这个流程,一步步完成小程序的免登.

二,获取code

按照官网的wx.login就可以获得这个code.

官网地址:developers.weixin.qq.com/miniprogram...

具体的使用如下:直接在项目根目录的app.vue文件中写就行:

js 复制代码
<script>
  export default {
    onLaunch: function() {
      console.log('App Launch')
      const token=uni.getStorageSync('token')
      if(!token){
        wx.login({
          success (res) {
            if (res.code) {
              //发起网络请求
              console.log("----",res.code)
            } else {
              console.log('登录失败!' + res.errMsg)
            }
          }
        })
      }
    },
    onShow: function() {
      console.log('App Show')
    },
    onHide: function() {
      console.log('App Hide')
    }
  }
</script>
​
<style>
  /*每个页面公共css */
</style>
​

然后在微信开发者工具中重新编译,查看控制台,就可以看到打印出来的code了.

diff 复制代码
---- 0f3Exu100SIhmQ12w3300wK26o3Exu11

三,新建接口接收这个code

现在我们已经获取到这个code了,接下来需要将code传递给我们的服务器.

于是我们首先需要新建这个服务端接口提前端调用.

3.1,新建login接口

依旧是使用云对象的方式,右键云服务器的云对象文件夹,新建云对象:

然后书写如下代码:

js 复制代码
  myLogin(param) {
    // 参数校验,如无参数则不需要
    if (!param) {
      return {
        errCode: 'PARAM_IS_NULL',
        errMsg: 'code不能为空'
      }
    }
    // 业务逻辑
    console.log("-------",param)
    // 返回结果
    return {
      param1 //请根据实际需要返回值
    }
  }

四,前端调用登录接口传递code

现在我们已经具备了后端登录接口,就需要在前端调用,将code传递给后端.

于是在刚刚app.vue文件中继续书写如下代码:

js 复制代码
<script>
  export default {
    onLaunch: async function() {
      console.log('App Launch')
      const token=uni.getStorageSync('token')
      
      if(!token){
        console.log("233333")
        wx.login({
          async success (res) {
            let result=res
            if (result.code) {
              //发起网络请求
              let params={
                code:result.code
              }
              const myLogin = uniCloud.importObject('login') // 导入云对象
              const res = await myLogin.myLogin(params)
              console.log("----",res)
            } else {
              console.log('登录失败!' + res.errMsg)
            }
            }
        })
      }
    },
    onShow: function() {
      console.log('App Show')
    },
    onHide: function() {
      console.log('App Hide')
    }
  }
</script>
​
<style>
  /*每个页面公共css */
</style>
​

通过这一步我们将code通过云对象(也就是我们常规前后端开发中的接口)的形式传递给了我们的服务器(云后台).

五,后端接收到code,调用微信接口

这里我们后端已经接收到code,按照文档,需要接着把code、appid、appsecret通过调用jscode2session接口来获取sessionKey和openId.

参考的官网地址:小程序登录 | 微信开放文档 (qq.com)

于是参照文档,我们在login云对象中书写如下代码:

js 复制代码
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
const createConfig = require('uni-config-center')
const shareConfig = createConfig({ // 获取配置实例
    pluginId: 'share-config' // 上文我们在common/uni-config-center下的插件配置目录名
})
const config = shareConfig.config() // 获取common/uni-config-center/share-config/config.json的内容
module.exports = {
  _before: function () { // 通用预处理器
​
  },
  /**
   * myLogin方法描述
   * @param {string} param 参数1描述
   * @returns {object} 返回值描述
   */
  async myLogin(param) {
    // 参数校验,如无参数则不需要
    if (!param) {
      return {
        errCode: 'PARAM_IS_NULL',
        errMsg: 'code不能为空'
      }
    }
    // 业务逻辑
    const URL = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.appid}&secret=${config.secret}&js_code=${param.code}&grant_type=authorization_code`
    const requestOptions = {
      method: 'GET',
      dataType: 'json'
    }
    const res1 = await uniCloud.httpclient.request(URL,requestOptions)
    if(res1.status==200){
      return res1.data
    }else{
      return {
        code:10001,
        msg:'从微信获取session_id失败'
      }
    }
  }
}

值得注意的是,这里我把appid和存储在了上一篇文章中说到的config.json中.

于是,这里我们在服务端调用微信接口,获取到了sessionKey和openId.

六,根据sessionKey和openId生成自定义的登录态

现在获取到sessionKey和openId,因为不建议直接暴露这两个参数给客户端,所以我们通常会利用这两个参数生成自己定义的登录态.

6.1,封装加解密方法

一般我们需要进行加密,考虑到大部分接口都需要进行加解密,于是可以在common中新建一个公共文件夹(右键common新建文件夹即可):common-boject,然后在该文件夹下安装加解密依赖:

npm install crypto-js

然后common下新建index.js文件,对加解密方法进行封装处理:

js 复制代码
const CryptoJS = require("crypto-js");
// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("1234567890123456");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("1234567890123456");
module.exports ={
  //加密
  encrypt (data) {
    if (typeof data === "object") {
      try {
        // eslint-disable-next-line no-param-reassign
        data = JSON.stringify(data);
      } catch (error) {
        console.log("encrypt error:", error);
      }
    }
    const dataHex = CryptoJS.enc.Utf8.parse(data);
    const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
      iv: SECRET_IV,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.ciphertext.toString();
  },
  //解密
  decrypt (data) {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
    const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
      iv: SECRET_IV,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
  }
}

6.2,在login云对象中引入加解密方法

在login云对象上右键-管理公共模块或扩展库依赖,勾选上文创建好的common库.

然后修改login云对象:

diff 复制代码
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
​
const createConfig = require('uni-config-center')
const shareConfig = createConfig({ // 获取配置实例
    pluginId: 'share-config' // 上文我们在common/uni-config-center下的插件配置目录名
})
const config = shareConfig.config() // 获取common/uni-config-center/share-config/config.json的内容
+ const {encrypt,decrypt} =require("common-object");
​
module.exports = {
  _before: function () { // 通用预处理器
  },
  /**
   * myLogin方法描述
   * @param {string} param 参数1描述
   * @returns {object} 返回值描述
   */
  async myLogin(param) {
    // 参数校验,如无参数则不需要
    
    if (!param) {
      return {
        errCode: 'PARAM_IS_NULL',
        errMsg: 'code不能为空'
      }
    }
    // 业务逻辑
    const URL = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.appid}&secret=${config.secret}&js_code=${param.code}&grant_type=authorization_code`
    const requestOptions = {
      method: 'GET',
      dataType: 'json'
    }
    const res1 = await uniCloud.httpclient.request(URL,requestOptions)
    if(res1.status==200){
+     const token=encrypt(JSON.stringify(res1.data))
+     const userList=await checkOpenID(res1.data.openid)
+     if(userList.length>=1){
+       //在user表中更新token,返回新token
+       return {
+         code:10000,
+         msg:'登录成功',
+         token:token,
+         id:userList[0]._id
+       }
+     }else{
+       //注册用户
+       const registerInfo=await register(res1.data.openid)
+       //返回token
+       return {
+         code:10001,
+         msg:'注册并登录成功',
+         token:token,
+         id:registerInfo[0]._id
+       }
+     }
    }else{
      return {
        code:10001,
        msg:'从微信获取session_id失败'
      }
    }
  }
}

七,新建用户数据表,根据openId查询数据表

在控制台新建user数据表:

然后在login云对象中新建注册用户的方法:获取该数据表,查询是否已存在用户,不存在则新增用户.

于是完整的login云对象变成:

js 复制代码
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
​
const createConfig = require('uni-config-center')
const shareConfig = createConfig({ // 获取配置实例
    pluginId: 'share-config' // 上文我们在common/uni-config-center下的插件配置目录名
})
const config = shareConfig.config() // 获取common/uni-config-center/share-config/config.json的内容
const {encrypt,decrypt} =require("common-object");
const db = uniCloud.database();
const userTable = db.collection('user');
const dbCmd = db.command
​
//注册
function register(openId){
  if(!openId){
    return
  }else{
    return new Promise(async (resolve,reject)=>{
      let nowTime=new Date()
      await userTable.add({userName:"momo",openId:openId,createTime:nowTime.toLocaleString()})
      const res=await checkOpenID(openId)
      resolve(res)
    })
  }
}
//用openId查询数据库
function checkOpenID(openId){
  return new Promise(async (resolve,reject)=>{
    let res = await userTable.where({
      openId:dbCmd.eq(openId)
    }).get()
    resolve(res.data)
  })
}
module.exports = {
  _before: function () { // 通用预处理器
  },
  /**
   * myLogin方法描述
   * @param {string} param 参数1描述
   * @returns {object} 返回值描述
   */
  async myLogin(param) {
    // 参数校验,如无参数则不需要
    
    if (!param) {
      return {
        errCode: 'PARAM_IS_NULL',
        errMsg: 'code不能为空'
      }
    }
    // 业务逻辑
    const URL = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.appid}&secret=${config.secret}&js_code=${param.code}&grant_type=authorization_code`
    const requestOptions = {
      method: 'GET',
      dataType: 'json'
    }
    const res1 = await uniCloud.httpclient.request(URL,requestOptions)
    if(res1.status==200){
      const token=encrypt(JSON.stringify(res1.data))
      const userList=await checkOpenID(res1.data.openid)
      if(userList.length>=1){
        //在user表中更新token,返回新token
        return {
          code:10000,
          msg:'登录成功',
          token:token,
          id:userList[0]._id
        }
      }else{
        //注册用户
        const registerInfo=await register(res1.data.openid)
        //返回token
        return {
          code:10001,
          msg:'注册并登录成功',
          token:token,
          id:registerInfo[0]._id
        }
      }
    }else{
      return {
        code:10001,
        msg:'从微信获取session_id失败'
      }
    }
  }
}

八,前端发起请求携带token

到目前为止,我们已经将自定义的token返回给前端,前端这时候则需要拿到token保存起来,并且在下一次接口请求时携带这个token.

于是在app.vue文件中就可以写:

diff 复制代码
      if(!token){
        console.log("233333")
        wx.login({
          async success (res) {
            let result=res
            if (result.code) {
              //发起网络请求
              let params={
                code:result.code
              }
              const myLogin = uniCloud.importObject('login') // 导入云对象
              const res2 = await myLogin.myLogin(params)
+             if(res2.code=10000){
+               uni.setStorageSync('token',res2.code)
+             }
            } else {
                console.log('登录失败!' + res.errMsg)
            }
            }
        })
      }

然后我们接着写一个页面,修改完善用户信息的.

第一步:我们首先需要调接口获取到用户信息

于是还是在login云对象中新建一个方法,作为接口.

js 复制代码
  //获取用户信息
  async getUserInfo(param){
    const {token}=param
    let result=JSON.parse(decrypt(token))
    const openId=result.openid
    const res =await checkOpenID(openId)
    return {
      code:10000,
      data:res[0],
      msg:'查询用户信息成功'
    }
  },

第二步:在前端页面中进行调用:

js 复制代码
<template>
  <view class="userinfo-box">
    <view class="header-box">
      <view class="item-box">
        <view class="left-title">
          昵称:
        </view>
        <view class="right-content">
          <input class="uni-input" focus v-model="name" />
        </view>
      </view>
      
    </view>
    <view class="bottom-btn" @click="save">
      保存
    </view>
  </view>
</template>
​
<script>
  export default {
    data() {
      return {
        name:''
      };
    },
    methods:{
      save(){
        console.log("保存修改",this.name)
      }
    },
    async onLoad(){
      const token=uni.getStorageSync('token')
        const myLogin = uniCloud.importObject('login') // 导入云对象
        const res2 = await myLogin.getUserInfo({
        token:token
      })
      if(res2.code=10000){
        this.name=res2.data.userName
      }
    }
  }
</script>
​
<style lang="scss" scoped>
.userinfo-box{
  background-color: pink;
  height: 100vh;
  padding: 20rpx 50rpx;
  box-sizing: border-box;
}
.header-box{
  margin: 100rpx 0;
  .item-box{
    display: flex;
    justify-content: space-between;
    
    .left-title{
      width: 120rpx;
    }
    .right-content{
      flex: 1;
      border-bottom: 1rpx solid #eee;
    }
  }
}
.bottom-btn{
  width: 70%;
  margin: 0 auto;
  text-align: center;
  height: 100rpx;
  line-height: 100rpx;
  background: skyblue;
}
</style>

实现的效果:

值得注意的是,小程序中,在onLaunch 中的请求是异步的,也就是说在执行 onLaunch 后页面 onLoad 就开始执行了,而不会等待 onLaunch 异步返回token后再执行,这就导致了页面无法拿到 onLaunch 中初次登陆的token。这明显是有问题的,我们想要的是在app.vue中的 onLaunch 执行完毕后再执行页面代码.

具体的解决方案可以看这篇文章:uni-app 中实现 onLaunch 异步回调后执行 onLoad 最佳实践 - 掘金 (juejin.cn)

我采用的是第一种方法.这个方法挺有意思的,其实就是利用promise.在vue的原型对象上存储一个promise,并且存储它的resolve方法.

然后就可以将resolve在其他地方使用了,于是页面上的代码只要使用个await就能等待onLaunch中的promise状态改变后再执行.

九,修改用户信息

现在我们已经拿到了用户的信息,并在前端页面上进行了修改,那么接下来,就需要把这个修改提交到数据库.

同样的,第一步需要编写后端接口,方便起见,我依旧是在login云对象中处理这段逻辑:

js 复制代码
  //修改用户信息
  async changeUserInfo(param){
    const {token,userName}=param
    let result=JSON.parse(crypt.decrypt(token))
    const openId=result.openid
    //先判断有没有同名的账号
    let res=await userTable.where({userName:dbCmd.eq(userName)}).get()
    if(res.data.length>0&& userName!='momo'){
      return {
        code:10000,
        msg:'该昵称已存在'
      }
    }else{
      const res2=await userTable.where({openId:dbCmd.eq(openId)}).update(
        {
          userName:userName
        }
      )
      return {
        code:10000,
        msg:'修改昵称成功'
      }
    }
  },

接着,在前端页面进行调用:

js 复制代码
async save(){
  const myLogin = uniCloud.importObject('login') // 导入云对象
  const res2 = await myLogin.changeUserInfo({
    token:this.token,
    userName:this.name
  })
  if(res2.code==10000){
    uni.showToast({
      title: res2.msg,
      icon:'error',
      duration: 2000
    });
  }
​
}

自此我们已经完成了微信小程序的登录,并在业务逻辑的接口中携带token.

大体的流程就是这样啦.

十,我的小程序体验

主要是一个文案记录的小程序,我自己使用的.可以扫码体验下.

相关推荐
YBN娜几秒前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=几秒前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck5 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!25 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。31 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼37 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093341 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架