Featured image of post 使用Uniapp实现用户登录并兼容游客模式

使用Uniapp实现用户登录并兼容游客模式

需求简介

用户首次登录–>没有缓存,给一个游客token,访问首页部分信息 –>点击进入登录页,输入手机号和验证码,将正式token存储到缓存中;关闭页面,重新进入,读取到缓存中的已登录的数据,正常进行页面请求和渲染。

目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
├── 📂pages
│   └── 📂tabbar
│		├── home.vue #首页
│   └── 📂login
│		├── login.vue #登录页
├── 📂store #vuex相关
│   └── 📂modules #vuex模块
│		├── user.js
│	├── index.js
├── App.vue
├── main.js
├── pages.json

Vuex状态管理

中大型项目中为了方便项目管理,尽量不直接在vuex中使用一个js进行状态管理,在这里我们使用模块化进行拆分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'

Vue.use(Vuex)

const store = new Vuex.Store({
	state: {},
	mutations: {},
	actions: {},
	modules: {
		user
	}
})

export default store

通过本地存储storage来持久化用户的登录状态和信息,并通过 Vuex 的状态管理功能来管理这些信息,即state定义状态、mutations里定义修改状态的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//modules/user.js
const user = {
    //	使用命名空间
    //	它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
	namespaced: true,
	state: {
		userInfo: {},
        // hasLogin 标识用户是否正式登录
		hasLogin: uni.getStorageSync('myToken') && uni.getStorageSync('hasLogin') == true ? true : false,
        visitorLogin: uni.getStorageSync('hasLogin') == true ? false : true,
        refreshToken:uni.getStorageSync('refreshToken') ? uni.getStorageSync('refreshToken') : '',
	},
	mutations: {
		login(state, provider) { //设置/改变登录状态
			state.hasLogin = true;
            state.visitorLogin = false;
			uni.setStorageSync('hasLogin', true);
			uni.setStorageSync('myToken', provider);
		},
        visLogin(state, provider) {
			state.hasLogin = false;
			state.visitorLogin = true;
			uni.setStorageSync('hasLogin', false);
			uni.setStorageSync('myToken', provider);
		},
		logout(state) { //退出登录
			state.hasLogin = false
			uni.clearStorage();
		},
        // 设置refreshToken
        refreshLogin(state, provider) {
			uni.setStorageSync('refreshToken', provider);
		},
	}
}
export default user

首页

vuex中定义好需要的变量之后,在首页读取缓存信息,判断用户登录状态。

如果是未登录,后端绝大部分的接口都是需要带token的,所以新用户要访问首页信息,需要先赋给祂一个游客临时token。

如果是已经登录,正常渲染页面,显示退出登录按钮。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
<template>
    <view>
        <view v-if="isvistor">
            <button @click="jumpLogin">游客状态去登录</button>
    	</view>
        <view v-else>
            <text>已经登录</text>
            <!-- 清除登录状态 -->
			<button type="primary" @click="clearLogin">退出登录</button>
    	</view>
    </view>
</template>
<script>
import { mapMutations } from 'vuex';
export default {
    computed: {
        hasLogin() {
            return this.$store.state.user.hasLogin
        },
        visitorLogin() {
            return this.$store.state.user.visitorLogin
        },
    },
    data() {
        return {
            isvistor: false,
        }
    },
    onLoad() {
        if (this.hasLogin && !this.visitorLogin) {
            console.log('用户正式登录,用正式token请求所有需要的信息')
            this.isvistor = false;
        } else {
            this.isvistor = true;
            // 获取游客的token
            this.loginByVistor();
        }
        
        // token过期处理
        // 假设约定的规则时 token过期时间小于24小时就刷新token
        if (this.isWithin24Hours(uni.getStorageSync('expire')) == true) {
            //使用setTimeOut模拟,在真实环境里这里应该是发送请求,从后端获取新的token
            setTimeOut(()=>{
                // console.log('刷新token成功')
                let newToken = 'asdfgjgduyfhsdfjhgf'
                let newRefreshToken = 'qwrtyweftufrgbnh'
                uni.setStorageSync('myToken', newToken);
                this.refreshLogin(newRefreshToken)
                uni.setStorageSync('expire', res.data.expire);
            }, 1000)
        }
        // token过期处理 END
        
    },
    methods: {
    	...mapMutations('user', ['login', 'visLogin', 'logout','refreshLogin']),
        loginByVistor() { //获取到游客身份的token
            let obj = {
                username: 'temp',
                password: '111111',
            }
            let token = uni.getStorageSync('myToken')
            // 获取临时token
            uni.request({  
                url: `${serverUrl}/temp`, 
                data: obj,  
                method: "POST",
                header: {
					'content-type': 'application/json',
					'Authorization': `Bearer ${token}`
				},
                success: (res) => {  
                	//登录成功后改变vuex的状态,并退出登录页面
                    if (res.code === 200) {
                        this.visLogin(res.data.token);
                        console.log('游客身份,用临时token请求游客可以看到的信息')
                    }
                }  
            })
        },
        jumpLogin() {
            uni.reLaunch({
                url: '/pages/login/login'
            })
        },
        // 退出登录/清除缓存
        clearLogin() {
            if (this.hasLogin) {
                this.logout()
                uni.reLaunch({
                    url: '/pages/login/login'
                })
            }
        },
        isWithin24Hours(expireTime) {
            const currentTime = new Date();
            const difference = Math.abs(expireTime - currentTime);
            const hoursDifference = Math.floor(difference / (1000 * 60 * 60));
            return hoursDifference <= 24;
        },
    }
}
</script>

登录页

登录页面输入用户名和密码后,发送请求获取到正式的token,存储到缓存中。然后跳转首页,在 home.vue 中根据hasLoginvisitorLogin,判断用户是临时游客还是正式登录,并进行对应页面内容的渲染。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<template>
	<view>
    	<view class="username">
        	<input placeholder="请输入手机号" v-model="phoneNumber" placeholder-style="color: #9ca3af;" />
        </view>
        <view class="pwd">
            <input placeholder="请输入密码" v-model="pwd" @confirm="doLogin" />
        </view>
        <view class="btn" @click="doLogin">登录</view>
	</view>
</template>

<script>
	import {
		mapState,
		mapMutations
	} from 'vuex';
	export default {
		computed: {
			hasLogin() {
				return this.$store.state.user.hasLogin
			},
		},
		data() {
			return {
				phoneNumber: "",
				pwd: '',
			}
		},
		onLoad() {
		},
		methods: {
			...mapMutations('user', ['login']),
			doLogin() {
				let obj = {
					phone: this.phoneNumber,
					pwd: this.pwd
				}
                // 获取正式token
                uni.request({  
                    url: `${serverUrl}/login`, 
                    data: obj,  
                    method: "POST",
                    header: {
                        'content-type': 'application/json',
                        'Authorization': `Bearer ${token}`
                    },
                    success: (res) => {
                        // 设置token--用正式token替换掉临时token
                        this.login(res.data.token);
                        
                        // 设置 refreshToken 和过期时间 expire
                        this.refreshLogin(res.data.refreshToken);
						uni.setStorageSync('expire', res.data.expire);
                        
                        uni.switchTab({
                            url: "/pages/tabbar/home"
                        })
                    }  
                })
			},
		}
	}
</script>
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy