谈谈对登录逻辑的理解
登录逻辑
1、第一次登录的时候,前端(客户端)调后端(服务器)的登陆接口,并发送用户名和密码
2、后端(服务器)收到(客户端)请求,验证用户名和密码,验证成功,就给前端(客户端)返回一个token
3、前端(客户端)拿到token,将token存储到localStorage或vuex中,并跳转路由页面
4、前端(客户端)每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、在组件中每次调后端(服务器)接口,都要在请求头中加token
6、后端(服务器)判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端(客户端)拿到状态码为401,就清除token信息并跳转到登录页面
具体步骤:
1、在vue项目中,找到src文件夹下的views文件夹,并在里面创建一个login文件夹(为了方便管理与维护),在里面创建Login.vue模板文件
2、在Login.vue中配置登录需求(在这里用的是vant组件库)
<template>
<div class="login_container">
<van-nav-bar
title="登录"
left-arrow
@click-left="onClickLeft"
/>
<div class="login_from">
<van-form>
<van-field
v-model="username"
name="手机号"
placeholder="请输入手机号"
>
</van-field>
<van-field
v-model="password"
type="password"
name="密码"
placeholder="请输入密码"
/>
<div style="margin: 0.8rem; margin-top: 1rem">
<van-button round block type="warning" @click="onSubmit">
登录
</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import { Toast } from "vant"; //引入文字提示
Vue.use(Toast);
export default {
data() {
return {
username: "",//用户名
password: "",//密码
};
},
methods: {
onClickLeft(){//点击返回上一页
this.$router.go(-1);
},
onSubmit(values) {
//点击登录
// console.log("submit", values);
//首先判断用户名和密码不能为空,如果为空就不提交请求
if (this.username.length == 0 || this.password == 0) {
Toast({
message: "输入的手机号或密码不能为空!!!",
position: "top",
});
return;
}
// 请求登录接口并配置参数
this.$axios.get("xxxxx",{params:{
username:this.username
password:this.password
})
.then((res) => {
// 请求成功,返回
console.log(res);
// 如果请求成功,服务器会返回一个token,我们要保存token
var token = res.data.token; //读取token
localStorage.setItem("token", token); //保存token
// 登录成功的提示
Toast.success({
message: "登录成功",
position: "top",
});
// 最后跳转页面
this.$router.push("/"); //跳转到首页
})
.catch((err) => {
// 请求失败,返回
console.log(err);
// 登录失败的提示
Toast.fail({
message: "登录失败",
position: "top",
});
});
}
},
};
</script>
<style lang="scss" scoped>
.login_container {
width: 100%;
height: 100vh;
}
.login_from {
width: 95%;
margin: 0 auto;
}
</style>
3、在配置全局路由守卫(在router文件夹下的index.js文件中配置),只有先登录后才能进入其他页面
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
// to: route路由对象 即将进入的目标路由
// from: route路由对象 当前导航正要离开的路由
// next: function函数 一定要调用这个函数来resolve这个钩子(放行函数)
router.beforeEach((to, from, next) => {
if (to.path === '/login') {//判断是否是进入登录页面,如果是直接放行
next();
} else {//如果不是,判断是否存在token
let token = localStorage.getItem('token');
if (token) {//如果有token,就直接放行
next();
} else {//如果没有token,就去登录页面
next('/login');
}
}
});
4、在组件中发送请求需要携带token,这样做主要是:用来作身份验证
4.1、我们需要新建一个request.js文件,在里面先引入axios模块,在创建axios实例
import axios from "axios";//引入axios模块
import API from "config.js";//引入请求类型
// import Vue from 'vue';
import { Toast } from 'vant';
import {Guid} from "./guid";//引入这个guid.js文件可以自动获取设备id
var id = localStorage.getItem("deviceid");//先读取
let deviceid = null;
if(id){
deviceid = id
}else{
deviceid = Guid.NewGuid().ToString("D");
}
localStorage.setItem("deviceid",deviceid)
// 创建实例
const instance = axios.create({
baseURL: 'http://xxxxx',//公用路径
timeout: 6000,//设置超时时间
});
// 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
//弹起loading控件
Toast.loading({
duration:0,
message: '加载中...',
forbidClick: true,
});
//读取token
var token = localStorage.getItem("token");
if(token){//容错判断
//在请求头中添加token
config.headers.authorization = `Bearer ${token}`;
}
//在请求头中添加设备id
config.headers.deviceid = deviceid;
config.headers.devicetype = "H5";
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
// 关闭loading控件
Toast.clear();
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
// 判断是get请求还是post请求
export function requset(type, url, params) {
switch (type) {
case API.type.GET:
return get(url, params);
case API.type.POST:
return post(url, params);
}
}
// 封装get请求
function get(url, params) {
return instance.get(url, params);
}
// 封装post请求
function post(url, params) {
return instance.post(url, params);
}
4.2、在创建一个config.js的文件
const API = {
// 配置API路径
url: {
// 例如:
// 轮播图
Banner: "/xxxxx",
},
type: {
GET: "get",//请求类型get请求
POST: "post",//请求类型post请求
}
}
export default API;
4.3、在创建一个index.js文件
import { requset } from "./core";
import API from "./config";//引入请求参数
const ClientAPI = {
// 封装请求函数(方法)
// 轮播图
//banner() {
// return requset(API.type.GET, API.url.Banner)
// },
}
export default ClientAPI;
4.4、在创建一个guid.js文件
//表示全局唯一标识符 (GUID)。
function Guid(g) {
var arr = new Array(); //存放32位数值的数组
if (typeof (g) == "string") { //如果构造函数的参数为字符串
InitByString(arr, g);
} else {
InitByOther(arr);
}
//返回一个值,该值指示 Guid 的两个实例是否表示同一个值。
this.Equals = function (o) {
if (o && o.IsGuid) {
return this.ToString() == o.ToString();
} else {
return false;
}
}
//Guid对象的标记
this.IsGuid = function () { }
//返回 Guid 类的此实例值的 String 表示形式。
this.ToString = function (format) {
if (typeof (format) == "string") {
if (format == "N" || format == "D" || format == "B" || format == "P") {
return ToStringWithFormat(arr, format);
} else {
return ToStringWithFormat(arr, "D");
}
} else {
return ToStringWithFormat(arr, "D");
}
}
//由字符串加载
function InitByString(arr, g) {
g = g.replace(/\{|\(|\)|\}|-/g, "");
//g = g.toLowerCase();
if (g.length != 32 || g.search(/[^0-9,a-f]/i) != -1) {
InitByOther(arr);
} else {
for (var i = 0; i < g.length; i++) {
arr.push(g[i]);
}
}
}
//由其他类型加载
function InitByOther(arr) {
var i = 32;
while (i--) {
arr.push("0");
}
}
/*
根据所提供的格式说明符,返回此 Guid 实例值的 String 表示形式。
N 32 位: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
D 由连字符分隔的 32 位数字 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
B 括在大括号中、由连字符分隔的 32 位数字:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
P 括在圆括号中、由连字符分隔的 32 位数字:(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
*/
function ToStringWithFormat(arr, format) {
let str;
switch (format) {
case "N":
return arr.toString().replace(/,/g, "");
case "D":
str = arr.slice(0, 8) + "-" + arr.slice(8, 12) + "-" + arr.slice(12, 16) + "-" + arr.slice(16, 20) + "-" + arr.slice(20, 32);
str = str.replace(/,/g, "");
return str;
case "B":
str = ToStringWithFormat(arr, "D");
str = "{" + str + "}";
return str;
case "P":
str = ToStringWithFormat(arr, "D");
str = "(" + str + ")";
return str;
default:
return new Guid();
}
}
}
//Guid 类的默认实例,其值保证均为零。
Guid.Empty = new Guid();
//初始化 Guid 类的一个新实例。
Guid.NewGuid = function () {
var g = "";
var i = 32;
while (i--) {
g += Math.floor(Math.random() * 16.0).toString(16).toUpperCase();
}
return new Guid(g);
}
export { Guid };
4.5、在mian.js文件中引入,最后注册下
import Vue from 'vue'
import './plugins/axios'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/vant.js'
import "./rem/rem.js";//引入rem
import ClientAPI from "./api/index";//引入封装的api
Vue.prototype.$ClientAPI = ClientAPI;//全局注册
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
5、在组件中使用
<script>
export default {
name: "",
data() {
return {
banner:[],//轮播图数据
};
},
mounted() {
//在组件中使用
//请求轮播图数据
this.$ClientAPI.banner().then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
},
methods: {
},
};
</script>