通过分析uni-id 插件,将可用的部分用于自己写的H5页面。
一、注册页面 reg.vue
- 注册页面的路径
pages/reg/reg.vue
1.1 点击注册按钮后触发register函数
- 注册请求发送到了 user-center 云函数
- 我们可以理解 user-center 是一个路由云函数
- action: ‘register’ 是向服务器说明了是 注册行为
- 注册成功了,就本地保存 “用户名” 和 “token”
- 页面跳转至 主页 main.vue `
methods: {
register() {
const data = {
username: this.username,
password: this.password
};
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'register',
params: data
},
success(e) {
if (e.result.code === 0) {
uni.showToast({
title: '注册成功'
});
uni.setStorageSync('uni_id_token', e.result.token)
uni.setStorageSync('username', e.result.username)
uni.reLaunch({
url: '../main/main',
});
} else {
uni.showModal({
content: JSON.stringify(e.result),
showCancel: false
})
}
},
fail(e) {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
}
})
}
}
1.2 云函数 user-center 接收注册信息并返回结果
uniID = require('uni-id')
=>uniIDIns = uniID.createInstance({ })
- 从
uniIDIns.register(params)
可以看出,请求又指向了uni-id
- 无需理解 uni-id 如何处理数据,返回值 res.result 包含了全部所需
`我们不用去研究 uni-id 的代码,只需要知道
res.result.code === 0 就表示注册成功了
res.result 包含了我们需要的全部信息`
const uniID = require('uni-id')
const db = uniCloud.database()
exports.main = async (event, context) => {
const uniIDIns = uniID.createInstance({
context
})
let params = event.params || {}
switch (event.action) {
case 'register':
res = await uniIDIns.register(params);
break;
}
}
二、主页 main.vue
- 主页路径
pages/main/main.vue
2.1 onLoad页面初始化
- 这里需要用到 vuex 的内容,公用函数
login
公用数据'forcedLogin', 'hasLogin', 'userName'
computed: mapState(['forcedLogin', 'hasLogin', 'userName'])
methods: { ...mapMutations(['login']) }
uni_id_token
保存在 token 信息- 有本地token时,调用云函数
user-center
向服务器表明了checkToken
是 检测token行为 loginType === 'local'
应该是APP用的,H5 页面loginType 始终 === 'online'
- 没有本地token时,调用
this.guideToLogin()
,要求必须登录,调用了工具类函数univerifyLogin()
,是APP、小程序才能用的一体登录系统 - 请求数据时并未看到传递参数,根据官方文档,参数已经自动传送了
- 检测 token 的官方文档,截图如下:
onLoad() {
const loginType = uni.getStorageSync('login_type')
//loginType === 'local' `应该是APP用的,H5 页面 `loginType 始终 === 'online'
if (loginType === 'local') {
this.login(uni.getStorageSync('username'))
return
}
let uniIdToken = uni.getStorageSync('uni_id_token')
if (uniIdToken) {
this.login(uni.getStorageSync('username'))
//调用云函数 user-center 向服务器表明了是检测 token
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'checkToken',
},
success: (e) => {
console.log('checkToken success', e);
if (e.result.code > 0) {
//token过期或token不合法,重新登录
if (this.forcedLogin) {
uni.reLaunch({
url: '../login/login'
});
} else {
uni.navigateTo({
url: '../login/login'
});
}
}
},
fail(e) {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
}
})
} else {
this.guideToLogin()
}
}
2.2 user-center处理 checkToken
- 无需关注代码处理逻辑,只需知道结果里有
payload
表明检测失败 - 成功或者失败,都会跳转到 登录页面
res.result
返回的结果,如图:
//表示检测失败
payload = await uniIDIns.checkToken(event.uniIdToken)
if (payload.code && payload.code > 0) {
return payload
}
//表示检测成功
switch (event.action) {
case 'checkToken':
res = await uniIDIns.checkToken(event.uniIdToken);
break;
}
三、登录页面 login.vue
- 登录页路径
pages/login/login.vue
3.1 onLoad()初始化数据
this.captcha('createCaptcha')
向 user-center 请求 createCaptchacase 'createCaptcha':res = await uniCaptcha.create(params)break;
const uniCaptcha = require('uni-captcha')
可以看出请求转到了uni-captcha
图文验证码模块- 图文验证码模块 的运行逻辑
<template>
/*neddCaptcha控制着图文验证码的显示
neddCaptcha 来自于 uni-needCaptcha 是一个布尔值
uni-needCaptcha 来自于 login 请求获得的
当登录验证成功时,就等于 false ; 当登录验证失败时,就等于 e.result.needCaptcha 也就是 true
当为 true 时,调用了 this.captcha('createCaptcha'),也就是获取图文码的网络地址,最终显示到img中
*/
<view v-if="needCaptcha" class="input-row">
<text class="title">验证码:</text>
<m-input type="text" v-model="captchaText" placeholder="请输入验证码"></m-input>
<view class="send-code-btn captcha-view" @click="captcha('refreshCaptcha')">
<i v-if="captchaing" class="uni-icon_toast uni-loading"></i>
<img v-if="!captchaing" :src="captchaBase64" width="100%" height="100%"></img>
</view>
</view>
</template>
<script>
import {mapState,mapMutations} from 'vuex'
import {getDeviceUUID} from '@/common/utils.js'
const captchaOptions = {deviceId: getDeviceUUID(),scene: 'login'}
data() {
return {
needCaptcha: uni.getStorageSync('uni-needCaptcha')
}
},
onLoad() {
if (this.needCaptcha) {
this.captcha('createCaptcha')
}
}
async captcha(action, args) {
if (this.captchaing) return;
// 验证不loading
this.captchaing = true;
let {result: res} = await uniCloud.callFunction({
name: 'user-center',
data: {
action,
params: {
...captchaOptions,
...args
}
}
})
this.captchaing = false;
if (res.code === 0) {
this.captchaBase64 = res.captchaBase64
} else {
uni.showToast({
icon: 'none',
title: res.message,
duration: 1000
})
}
return res;
}
</script>
3.2 onReady()
this.initProvider()
调用了uni.getProvider()
在H5页面无效,参考下方官方文档- uni.getProvider官网文档
this.initPosition();调取了 uni.getSystemInfoSync()
防止页面CSS样式变形
onReady() {
this.initPosition();
this.initProvider();
// #ifdef MP-WEIXIN
this.isDevtools = uni.getSystemInfoSync().platform === 'devtools';
// #endif
}
3.3 点击登录按钮触发的函数
bindLogin()
切换短信验证码登录
或者密码登录
bindLogin() {
switch (this.loginType) {
case 0:
this.loginBySms()
break;
case 1:
this.loginByPwd()
break;
default:
break;
}
}
3.4 账号,密码登录
loginByPwd()
调用了云函数user-center
标明了是login
登录行为
async loginByPwd() {
const data = {
username: this.username,
password: this.password,
captcha: this.captchaText,
...captchaOptions
};
this.loginBtnLoading = true
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'login',
params: data
},
success: (e) => {
if (e.result.code == 0) {
this.needCaptcha = false;
uni.setStorageSync('uni-needCaptcha', this.needCaptcha)
uni.setStorageSync('uni_id_token', e.result.token)
uni.setStorageSync('username', e.result.username)
uni.setStorageSync('login_type', 'online')
uni.setStorageSync('uni_id_has_pwd', true)
this.toMain(this.username);
} else {
uni.showModal({
content: e.result.message,
showCancel: false
})
this.needCaptcha = e.result.needCaptcha;
uni.setStorageSync('uni-needCaptcha', this.needCaptcha)
if (this.needCaptcha) {
this.captcha('createCaptcha')
}
}
},
fail: (e) => {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
},
complete: () => {
this.loginBtnLoading = false
}
})
}
toMain(userName) {
this.login(userName);
/**
* 强制登录时使用reLaunch方式跳转过来
* 返回首页也使用reLaunch方式
*/
uni.reLaunch({
url: '../main/main',
});
}
3.5 user-center 登录请求
switch (event.action) {
case 'login':
let passed = false;
let needCaptcha = await getNeedCaptcha();
if (needCaptcha) {
res = await uniCaptcha.verify(params)
if (res.code === 0) passed = true;
}
if (!needCaptcha || passed) {
res = await uniIDIns.login(params);
await loginLog(res);
needCaptcha = await getNeedCaptcha();
}
res.needCaptcha = needCaptcha;
break;
}
四、总结
- 将整套uni-id插件导入编辑器,上传所有云函数及公用函数。
- pages页面全部删除,自建主页,登录页,注册页,修改密码页。
- 保留全部VUEX部分
- common 工具类函数保留 utils.js
4.1 主页设置
onLoad()
即开始检测 token- 本地
uni_id_token
保存着 token 信息,分为两种情况:有token和没有token - 本地 token 正确也未过期,修改
VUEX
状态this.login(uni.getStorageSync('username'));
- 本地 token 错误或者过期了,跳转登录页面
uni.reLaunch({url: '../login/login'});
- 本地没有
token
,跳转至登录页面 - 请求模板
uniCloud.callFunction({name: 'user-center',data: {action: 'checkToken'})
import { mapState, mapMutations } from 'vuex';
export default {
computed: mapState(['userName', 'hasLogin']),
data() {
return {
};
},
onLoad() {
let uid = { uid: `${uni.getStorageSync('user_id')}` };
let uniIdToken = uni.getStorageSync('uni_id_token');
if (uniIdToken) {
//有 token 的情况下,开始检测
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'checkToken'
},
success: e => {
if (e.result.code > 0) {
//检测 token 有问题,返回登录页
uni.reLaunch({
url: '../login/login'
});
}
this.login(uni.getStorageSync('username'));
},
fail(e) {
uni.showModal({
content: e.msg,
showCancel: false
});
}
});
} else {
//没有 token 的情况下,返回登录页
uni.showModal({
content: '请登录后再浏览',
showCancel: false,
success: res => {
if (res.confirm) {
uni.reLaunch({
url: '../login/login'
});
}
}
});
}
},
methods: {
...mapMutations(['login']),
login_out() {
uni.reLaunch({
url: '../login/login'
});
}
}
};
4.2 登录页面设置
- 登录后,保存了
token
、username
、user_id
,然后跳转到主页 - 登录密码错误后的图文验证码模块
user_id
将用于删除云数据库uni-id-log
中过多的、重复的登录记录。 文末有删除记录的云函数方法。- 请求模板
uniCloud.callFunction({name: 'user-center',data: {action: 'login',params: data})
- 注意请求的参数格式
{username:'',password:'',captcha:'',...captchaOptions}
import {mapState,mapMutations} from 'vuex';
import {getDeviceUUID} from '@/common/utils.js';
const captchaOptions = {deviceId: getDeviceUUID(),scene: 'login'};
export default {
computed: mapState(['userName','forcedLogin']),
data() {
return {
setHeight: '',
isShow: false,
dataIndex: '1',
tipMsg: '提示:用户名,中英文均可',
nameTrue: false,
wordTrue: false,
againTrue: false,
nameVal: '',
wordVal: '',
againVal: '',
needCaptcha: uni.getStorageSync('uni-needCaptcha'),
captchaing: false,
captchaBase64: '',
captchaText: ''
};
},
onLoad() {
this.setHeight = `height:${uni.getSystemInfoSync().windowHeight}px;`;
if(uni.getStorageSync('username')){
this.nameVal = uni.getStorageSync('username');
}else{
uni.showModal({
content:'请您先注册或者登录',
showCancel: false
});
}
if (this.needCaptcha) {
this.captcha('createCaptcha')
}
},
methods: {
...mapMutations(['login']),
loginByPwd() {
const data = {
username: this.nameVal,
password: this.againVal,
captcha: this.captchaText,
...captchaOptions
};
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'login',
params: data
},
success: (e) => {
if (e.result.code == 0) {
console.log('登陆成功');
this.needCaptcha = false;
uni.setStorageSync('uni-needCaptcha', this.needCaptcha);
uni.setStorageSync('uni_id_token', e.result.token);
uni.setStorageSync('username', e.result.username);
uni.setStorageSync('user_id', e.result.uid);
this.toMain(this.nameVal);
} else {
uni.showModal({
content: e.result.message,
showCancel: false
})
}
this.needCaptcha = e.result.needCaptcha;
uni.setStorageSync('uni-needCaptcha', this.needCaptcha)
if (this.needCaptcha) {
this.captcha('createCaptcha')
}
},
fail: (e) => {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
}
})
},
toMain(userName) {
this.login(userName);
uni.reLaunch({
url: '../min_main/min_main',
});
},
async captcha(action, args) {
if (this.captchaing) return;
// 验证不loading
this.captchaing = true;
let {result: res} = await uniCloud.callFunction({
name: 'user-center',
data: {
action,
params: {
...captchaOptions,
...args
}
}
})
this.captchaing = false;
if (res.code === 0) {
this.captchaBase64 = res.captchaBase64
} else {
uni.showToast({
icon: 'none',
title: res.message,
duration: 1000
})
}
return res;
}
}
};
4.3 注册请求
- 对于用户名和密码的格式要求,需自己写。
- 注意:注册时发送给云函数
user-center
的data
数据的格式data:{action:'register',params: 账号,密码}
- 请求模板
uniCloud.callFunction({name: 'user-center',data: {action: 'register',params: data})
gotoRegister() {
uni.showLoading({
title: '正在提交,请稍后'
});
const data = {
username: this.username,
password: this.password
};
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'register',
params: data
},
success(e) {
uni.hideLoading();
if (e.result.code === 0) {
uni.showToast({
title: '注册成功'
});
uni.setStorageSync('uni_id_token', e.result.token);
uni.setStorageSync('username', e.result.username);
uni.reLaunch({
url: '../main/main'
});
} else {
uni.showModal({
content: JSON.stringify(e.result),
showCancel: false
});
}
},
fail(e) {
uni.hideLoading();
uni.showModal({
content: JSON.stringify(e),
showCancel: false
});
}
});
}
4.4 修改密码页
请求模板
uniCloud.callFunction({name: 'user-center',data: {action: 'updatePwd',params: data})
传递的参数是对象,{newPassword:123456,oldPassword:654321,uid:'用户ID'}
gotoCheck(e) {
var that = this;
uni.showLoading({
title: '正在提交,请稍后'
});
e.newPassword = that.againVal;
e.oldPassword = that.nameVal;
e.uid = uni.getStorageSync('user_id');
uniCloud.callFunction({
name: 'user-center',
data: {
action: 'updatePwd',
params: {
...e
}
},
success: (res) => {
uni.hideLoading()
if (res.result.code === 0) {
uni.showModal({
title: '提示',
content: res.result.msg,
showCancel: false,
success: (res) => {
if (res.confirm) {
that.logout();
uni.removeStorageSync('uni_id_token')
uni.removeStorageSync('username')
uni.reLaunch({
url: '/pages/main/main'
})
}
}
});
} else {
uni.showToast({
title: res.result.msg,
icon: 'none',
duration: 2000
})
}
},
fail: (e) => {
uni.hideLoading()
uni.showModal({
content: '修改密码失败',
showCancel: false
})
}
})
}
}
4.4 补充
- 请求
user-center
函数可以进一步封装,调用时只传参,比如action,params都可以传参。这里就不做精简演示了。 - 删除登录记录的云函数,删除验证码云端记录。
- 获取当前
user_id
的全部登录记录数据,筛选出create_date
值不是最大的全部记录,逐一删除。 - 登录成功后,删除集合
opendb-verify-codes
的全部记录
4.4.1 云函数 removeSth
'use strict';
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
if (Array.isArray(event)) {
if (event.length===0) {
const resList = await db.collection('uni-id-users')
.where({
username: dbCmd.neq('这里填管理员账号')
})
.get();
let userArr = resList.data;
return {
msg: '查询用户列表,成功',
code: 200,
userArr
}
} else {
event.forEach(k=>{
db.collection('uni-id-users').doc(`${k}`).remove();
})
const resList = await db.collection('uni-id-users')
.where({
username: dbCmd.neq('这里填管理员账号')
})
.get();
let userArr = resList.data;
return {
msg:'查询并删除',
userArr
}
}
} else{
const {uid} = event;
db.collection('opendb-verify-codes').where({
_id: dbCmd.exists(true)
}).remove();
let resList = await db.collection('uni-id-log')
.where({
user_id: `${uid}`
})
.get();
let res = resList.data;
if (res.length > 1) {
let num = 0;
res.forEach((item) => {
if (item.create_date > num) {
num = item.create_date;
}
})
let resObjArr = await db.collection('uni-id-log')
.where({
create_date: dbCmd.neq(num)
})
.get();
resObjArr.data.forEach(async (item) => {
let resSucc = await db.collection('uni-id-log').doc(`${item._id}`).remove();
console.log(resSucc);
});
const date = new Date().getTime();
res = await db.collection('uni-id-log')
.where({
user_id: `${uid}`
})
.get();
return {
msg: '删除成功',
code: 200
}
} else {
return {
msg: `当前账号仅有一次登录记录`,
code: 200
}
}
}
};
4.4.2 在common
内创建 removeSth.js
const removeSth = (mydata) => {
return new Promise((reslove, reject) => {
uniCloud.callFunction({
name: 'removeSth',
data: mydata
})
.then(
(res)=>{
reslove(res.result)
},
(err)=>{
reject(err)
})
.catch(err => {
console.log(err);
})
});
};
export {
removeSth
}
4.4.3 页面调用
import {removeSth} from "../../common/removeSth.js";
let uid = { uid: `${uni.getStorageSync('user_id')}` };
removeSth(uid).then(v=>{
console.log(v);
})