一,微信程序的登录流程概述
其实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.
大体的流程就是这样啦.
十,我的小程序体验
主要是一个文案记录的小程序,我自己使用的.可以扫码体验下.