前言:接下来的内容就是前端页面了,因为也只是用于做登录和注册功能测试的,所以并不会很详细的说。全部的展示会在接下来更新的文章:03-前端美化篇 04-Socket.io 核心功能实现篇 05-Shotalk 项目完全体整合,06-服务器挂载上线,为啥分了这么多篇,原因就是我把整个登录注册功能分离出来说了一手,因为确实弄了很久。并且也是这个项目的第二大核心功能。
展示:(先来看一下的网站页面)
说一下这个只是用于做功能测试的,里面有太多太多的东西还需要去优化,这个只是做的基础样式。
首先是首页:
首页的其他一些地方:
登录页:
注册页:
个人主页:
一. vue 项目搭建
1.1 vue 环境搭建以及项目初始化
网上教程一大堆,这里就没啥好说的了。
这里就说一个地方,我在安装例如:eslint.js 或是 axios 时用的不是命令行安装的,用的是vue 一个可视化界面(GUI)这个是伴随 Vue CLI3.0 一起发布的一个工具吧应该是,很方便,好处就是在你安装完需要的依赖,例如 vuex 时,它会自动帮你全局引入,然后自动新建 store 文件目录,大概长这样:
1.2 完成项目基础骨架搭建
完整的项目结构:
1.3 初始 vue 项目显示逻辑
· 首先前端项目的入口都是 public 下的 index.html 文件
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<!-- 浏览器中正文部分所显示的内容 -->
<title>Shotalk 即时通讯</title>
</head>
<body>
<!-- id 为 app 的挂载点 -->
<div id="app"></div>
</body>
</html>
· 如上所示有一个 id 为 app 的挂载点,之后 Vue 的根实例就会挂载到该挂载点上
· 之后程序运行 index.html 之后会去寻找用到的 app 的文件,就是 main.js
main.js
这里创建了一个 vue 实例,挂载了 #app,也就是如上所说 index.html 中 id 为 app 的元素
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App) // 提交,将 App.vue 这个组件通过 new 这个实例提交上去
}).$mount("#app"); // 挂载,将实例挂载到 index.html 的 id 为 app 的位置
· 紧接着就会加载 App.vue 的页面
App.vue
· 根据 App.vue 中含有 router-view 标签的地方,会动态将对应的组件内容渲染到 router-view 中
总的来说:
项目运行起来后,会先去找入口文件index.html,在index.html中找到其实例需要挂载的位置,然后找到main.js中实例创建的地方,去加载组件中的模板,然后通过模板中的router-view,得知需要去查找路由,找到后加载对应的模板,如果模板中引用了公共的模板再加载公共模板进来,最后把处理后的这些呈现到浏览器中。
二. 登录和注册功能逻辑分析(重点)
说这个地方非常重要是因为不管在做什么需求实现的时候,一定要弄明白你要实现的整个逻辑,要不然在写代码的时候就是一锅粥,不要小看这个功能,这个也属于是项目中的核心功能。
2.1 登录验证流程
在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到 localStorage 和vuex中,并跳转路由页面(根据 token 的用户状态,进行导航栏的变化)
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、触发退出登录时,就清除token信息并跳转到登录页面
三. 封装 axios 拦截器和路由守卫(重点中的重点)
这个地方我必须要说一下,就这的实现我大概弄了有半个月(从学到用)有很大一部分原因是因为我在学的时候犯了一个非常非常低级的错误,就是没有像我以前学别的技术时去看官方文档学,而是去博客和 b 站上搜,结果搞了很久都没有弄出个人样出来,(中间有几次真的破防开始摆烂了,连玩三天游戏)其一是我有点过于低估封装的思想了,其二是因为我有点过于高估自己 js 的水平了。后来在一个师兄的帮助下(最后会说)终于是搞了个雏形出来,本来是可以直接用的,但是我还不是很满意,于是就去 git 了几个项目看了一下,最后加了点自己的二次封装,最后是才出来这个人样出来,但是其实还是很一般hhhh,好了可以了别骂了。
总的来说就是自己脑血栓没去看官方文档,自己一个人闭门造车。
贴一下 axios 和 vuex 的官方文档地址:
axios:https://www.axios-http.cn/
vuex:https://vuex.vuejs.org/zh/guide/
OK 进入正题:
3.1 axios 封装以及请求响应拦截器的定义
request.js
// 导入 axios
import axios from "axios";
import { Axios } from "axios";
// 引入 swal 组件进行消息提示
import swal from "sweetalert";
// 创建新的 axios 实例
const request = axios.create({
// 公共接口
baseURL: "http://localhost:7777",
timeout: 5000
});
/*
* 请求拦截器(所有的网络请求都会执行此方法)
* 如果存在 token 则在请求头上加入 token
* */
request.interceptors.request.use(config => {
const token = window.localStorage.getItem("token");
// 请求拦截器测试
console.group("全局请求拦截器");
// 判断 token 是否存在
if (token) {
// 如果存在,将其加载到请求头上
config.headers.Authorization = "Bearer" + token;
console.log(config);
}
return config;
});
// 响应拦截器
// 使用 response 拦截器.
axios.interceptors.response.use(
response => {
return response;
},
// 对 2xx 状态码以外的结果进行拦截。
error => {
// 401: 身份过期或 token 无效
if (error.response.status === 401) {
// 移除本地存储的 token
window.localStorage.clear();
// 跳转到登录页面登录
this.$router.push("/login");
}
}
);
export default request;
由于我没有用 element-ui 组件,所以 alert 消息提示用的是第三方的一个组件:sweetalert 这个是官网:https://www.sweetalert.cn/
3.3 在页面中使用
Login.vue 组件下 login 方法的定义。
// 引入定义封装好的 request
import request from "@/api/request";
export default {
data() {
return {
loginForm: {
username: this.username,
password: this.password
},
boolean: false
};
},
methods: {
login() {
// 使用封装好的 axios 进行请求
request.post("/login", this.loginForm).then(res => {
if (res.data.code === 200) {
// 全局存储 token
window.localStorage["token"] = JSON.stringify(res.data.token);
swal("登录成功");
this.$router.push("/");
// 跳转成功后,重新刷新界面
window.location.reload();
}
});
},
3.4 拦截器和请求头测试
拦截器测试
查看请求头是否加入成功
检查浏览器中是否拿到 token
3.4 路由守卫
定义路由守卫:
所谓的路由守卫可以简单的理解为一座房子的门口的保安,想要进入这个房子就必须通过保安的检查,要告诉路由守卫你从哪里来?总不能随便陌生人就给放进去?要到哪里去?然后保安再告诉你下一步该怎么做?如果你的确是这个房子主人允许进入的人,那就让你进入,否则就要打电话给房子主人,跟房主商量(登录注册),给你权限。
router.js 下的 beforeEach 方法定义
router.beforeEach((to, from, next) => {
let token = window.localStorage.getItem("token");
// 判断访问页面权限
if (to.meta.requiresAuth) {
// 如果 token 存在
if (token) {
next();
} else {
next({
path: "/login",
query: { redirect: to.fullPath }
});
}
} else {
next();
}
});
示例:比如个人页必须要登录后才能访问
在 router.js 中更改组件中的 meta 将 requiresAuth 值改为 true,这样在未登录的情况下访问时,导航守卫会跳转到 login 页。
四. 根据用户状态更改视图
4.1 导航栏定义
MainNavbar.vue(导航栏)
如上使用 v-if 进行条件判断,根据不同的用户状态显示不同的视图内容。
<!-- 用户登录后显示的导航栏 -->
<div class="login-part" v-if="hasLogin">
<li class="md-list-item">
<a
href="javascript:void(0)"
class="md-list-item-router md-list-item-container md-button-clean dropdown"
>
<div class="md-list-item-content">
<drop-down direction="down" class="profile-photo">
<div
class="profile-photo-small"
slot="title"
data-toggle="dropdown"
>
<img :src="img" alt="Circle Image" />
</div>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a href="/profile" class="dropdown-item">我的主页</a>
</li>
<li>
<a href="/setting" class="dropdown-item">设置</a>
</li>
<li>
<a @click="logout" class="dropdown-item">退出登录</a>
</li>
</ul>
</drop-down>
</div>
</a>
</li>
</div>
<!-- 用户未登录前显示的导航栏 -->
<div class="hasLogin" v-else>
<li class="md-list-item">
<a
href="javascript:void(0)"
class="md-list-item-router md-list-item-container md-button-clean dropdown"
>
<div class="md-list-item-content">
<md-button
@click="toLogin"
slot="title"
class="md-button md-button-link md-white md-simple"
>
<i class="iconfont icon-denglu-copy"></i>
<p>登录</p>
</md-button>
<md-button
@click="toRegister"
slot="title"
class="md-button md-button-link md-white md-simple"
>
<i class="iconfont icon-zhuce"></i>
<p>注册</p>
</md-button>
</div>
</a>
</li>
</div>
定义 method 方法检查登录状态
checkLogin() {
const token = window.localStorage.getItem("token");
this.hasLogin = token != null;
},
通过 token 进行用户状态判断
mounted() {
// 判断登录状态
this.checkLogin();
},
4.2 视图变换测试
未登录状态下
登录后的状态
4.3 总结:
总的来说呢,其实做的挺一般的,因为不难看出我所有的用户状态的改变都是根据操作 localStorage 中的 token 去实现的,也就是通过 token 的存活状态去操作用户状态,这个操作相当的新手,并没有用到 vuex 。我看了很多比较优秀的项目是如何去做的。
简单说一下:大概是会将 login 这个操作单独封装成一个单独的 api:login.js 用户状态管理封装成一个 user.js 里面包含 login 的还有 logout 等等方法和保存 userinfo 的方法,通过 commit 提交用户信息来修改用户状态,大概是这么个流程,我也写了很多 demo 去测试,但是中间出了挺多问题的,而且好像是有一点安全隐患的,反正这里面名堂挺大的,所以还在研究当中,所以上述的是我目前能力范围内能写出来最成熟的方法了,但是我有信心最后一定可以弄出来一套比较成熟的封装方案的,只不过现在确实真的能力有限。
五. 前后端数据交互
5.1 前后端的传值字段
通过 api 测试可以看出,在登录功能中,前端传给后端包含两个字段:
{
"username": "111",
"password": "111",
}
前端通过访问登录页面向 向 http://localhost:8080/login POST username和password。这个可以在 login.vue 组件下的 login 方法中看到:
methods: {
login() {
// 使用封装好的 axios 进行请求
request.post("/login", this.loginForm).then(res => {
if (res.data.code === 200) {
// 全局存储 token
window.localStorage["token"] = JSON.stringify(res.data.token);
swal("登录成功");
this.$router.push("/");
// 跳转成功后,重新刷新界面
window.location.reload();
}
});
},
之后后端会向前端返回结果
· 如果成功的话
{
"code": 200,
"message": "登录成功",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImV4cCI6MTY1Mjc4MjU1NSwiaWF0IjoxNjUyNjk2MTU1fQ.qLSkV3EJHYndSd5d28E4_CUuOu_pumeb9Y6tV4aUjas"
}
· 如果失败的话
{
"code": 1002,
"message": "用户名或密码不存在",
"token": null
}
这个详细参见上一篇后端部分。
5.2 json 数据传递和网络请求逻辑
request.post("/login", this.loginForm).then(res => {}
在这里使用到的 request 就是前面封装好的 axios 请求,直接把实例化 axios 引入。
import request from "@/api/request";
也就是说,在 api 目录下的 request ,可以使用axios的get,post等等,也可以通过向axios传递相关配置来创建请求。loginForm 对应的就是传递需要传递的参数
data() {
return {
loginForm: {
username: this.username,
password: this.password
},
};
},
这里的参数在对应到页面中的表单内容。
六. 总结:
写在最后:
首先我要感谢一下我们学校的一位师兄(现在已经实习了),真的给了我太多太多的帮助了,因为我对于 vue 并不是很了解,由于 vue 这个技术算是比较新的框架,我们学校的很多老师(反正我认识的全部都问了一遍,貌似是没有会的更别说用过了)网上的一些课也没有具体去解决我所遇到的问题的。所以直接上手去做是真的差太多,关于帮人这件事情我觉得有必要说一下,别人不帮你是本分,帮你是情分,后面有些地方,我也觉得不好意思再去打扰他了。但是真的非常感谢他。
说一下做到这里的感悟,也是我做开发的一个感受:没有人能一次性将一个项目写的非常完美,编程这件事情永远都是循循渐进的,没有最好只有更好,在优化优化的路上一直前进下去。我以前也犯过这样的错误,看到那么多新技术都想应用到自己的项目中,但每次都是事宜愿违,熟练度只会更好的帮你少犯低级错误。还是需要你对你现在所做的事情富有激情的。说的有点多了,总之这就是前端项目登录逻辑的分析,写的很糙,见谅一下,毕竟只是留个念想。
彩蛋:(不怕笑话,贴一下我设计的页面初始样子)
这里贴一下我设计的页面初始,(这个是我在图书馆花了大概 10 分钟手画的,md 现在想起来自己真是个弱智)