本文介绍了如何使用vue-element-admin开发在线考试系统教师端的流程,模板的下载过程略过,项目部署后的目录结构如图所示
我自己的前端学的不怎么样,可以说是个半吊子,只能粗略的用一下,文章主要是针对那些对后端比较了解,但是前端了解的比较少,只需要会用就行的人,帮你们少走点弯路,都是自己踩了很多坑走过来的。
前面几段是根据我自己的业务需求配置的,如果想要看登录的实现具体过程可以从登录页面初步修改章节看起
修改访问端口
项目文件如图所示,在vue.config文件中存放了配置文件,由于本项目所有前端页面都使用nginx为入口,所以需要配置项目的端口号,在该文件中找到port的配置信息,修改为3001
const port = process.env.port || process.env.npm_config_port || 3001 // dev port
跨域问题的解决
因为本地使用了域名模拟,所以需要解决跨域问题,在配置文件中修改如下信息
本项目后端使用了微服务的技术,要在网关中也允许跨域
通过teacher.exam.com域名访问后一直在请求info?t=1585471430974这个地址,具体原因可以自行百度,这里只提供了解决方案找到node_modules里的sockjs文件,将1605行注释
登录页面初步修改
具体的路由还有目录结构请自行查看官方网站,这里只截取需要更改的地方
登录页面对username进行了验证,这里我们需要改成自己的逻辑,本项目的用户名并没有限制,所以只进行了是否为空的验证
const validateUsername = (rule, value, callback) => {
if (value=='') {
callback(new Error('请输入用户名'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码不能小于6位'))
} else {
callback()
}
}
登录的提交方法代码如下,这里建议尽量不要修改他默认的代码,感觉作者在登录方面写的文档对于新手不太友好,也没有讲的非常清楚
这里登录的流程其实是对token进行了验证,在登录后,服务端会返回一个token,然后会自动调用getInfo方法获取用户的信息,主要是this.$store.dispatch('user/login', this.loginForm)
这行代码,映射了多个文件,需要了解逻辑的同学可以参考这篇文章https://www.jianshu.com/p/29c5b8cd593e
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
登录页面如下图所示
登录页面html模板代码
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">你的系统名称</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="请输入用户名"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="请输入密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
</el-form>
</div>
</template>
默认登录流程分析
这里我们只看默认情况下,是如何进行登录的,这里因为代码我已经是改好的,所以简单粗暴,只看他请求完返回了什么数据
1.第一个请求是获取token,请求路径也不再展示,可以看到在response的data中,是一个键值对形式的值,key为token,value是可以自定义的
2.第二个请求是根据token拿到用户的信息,这里面的数据比较多,还是Map形式的data数据,存放了roles,introduction,avtar,name,分别是用户的权限,用户的介绍,用户的头像和名称
知道了要返回什么数据,我们就可以动手写后台代码了,但是请求路径还是需要更改的,这里其实最后是调用的api下的user文件,前面放的那篇大佬的文章也有写为什么是这个文件,可以自己去看,我这里主要重前后端交互实操
修改user.js
export function login(data) {
return request({
url: 'login',
method: 'post',
data,
baseURL:'http://api.exam.com/api/auth/'
})
}
这里说明一下,method指定的是请求的方式,data为post请求的携带信息,我这里添加了baseURL是后端业务需求,可以自己定义,这样完整的请求路径是http://api.exam.com/api/auth/login
export function getInfo(token) {
return request({
url: 'info',
method: 'get',
params: { token },
baseURL:'http://api.exam.com/api/auth/'
})
}
这里params是get请求携带的参数,会放在url后面,这里基本上都是默认的代码,建议不要修改他的代码,后端根据这个来写就好
后台代码编写
这里直接上controller的代码
@PostMapping("login")
@ApiOperation(value="登录", notes="登录")
public Request login(@RequestBody Map<String,String> data,
HttpServletRequest request,
HttpServletResponse response) {
String username = data.get("username");
String password = data.get("password");
//登录
String token = this.authService.login(username, password);
if (StringUtils.isBlank(token)) {
System.out.println("没有token");
return new Request(false,400,"登录失败");
}
//CookieUtils.setCookie(request,response,cookieName,token,60*60,false);
Map<String,Object> tokenMap = new HashMap<>();
tokenMap.put("token",token);
return new Request(true,20000,"登录成功",tokenMap);
}
CookieUtils.setCookie(request,response,cookieName,token,60*60,false);
这个可以不需要,因为我是别的业务也是用这个来登录的,直接在后端给Cookie赋值的,这个前端项目是通过服务端生成token之后返回给他,然后由他来做Cookie的生成
authService.login
其实里面的业务逻辑也很简单,就是做一个简单的校验,然后生成一个token返回,这个需要看自己的业务需求进行修改
public String login(String username, String password){
try {
//校验用户名和密码
User user = userClient.queryUser(username, password);
if (user == null){
System.out.println("用户账号密码错误");
}
//生成token
String token = JwtUtils.generateToken(new UserInfo(user.getId(), username,user.getRole()), prop.getPrivateKey(), prop.getExpire());
return token;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
注:特别注意!!!!,这里返回code值必须是20000,我一开始返回200,一直纳闷明明已经获取数据成功了,还不会跳转,就是因为前端不知道哪里进行了控制,20000是请求成功的返回码,这里是自己太菜了,也不会改,如果有大佬知道在哪里修改,可以给我留言谢谢。其实看默认的执行流程就应该想到,如果跟着默认的流程修改,返回的数据肯定要是相似的,特别是状态码跟数据。
这里其实就基本成功了,看一下请求返回的数据,我自己增加了一个message和flag用来做请求的友好提示,这个Request类是自己封装的,可以根据自己需要,但是结构一定要跟默认流程保持一致
但是这样还不能进行跳转,还缺了获取用户信息的后端代码,这里其实是对token进行了解析,然后返回一个用户实体信息
@GetMapping("info")
public Request getInfo(@RequestParam("token") String token){
try {
//解析token
UserInfo info = JwtUtils.getInfoFromToken(token,prop.getPublicKey());
return new Request(true,20000,"已登录",info);
}catch (Exception e){
return new Request(false,202,"token已过期");
}
}
UserInfo类
public class UserInfo {
private Long id;
private String username;
private int role;
}
这样就可以顺利登陆了,看一下实际返回给前端的信息
解决首页没有name值
如果跟着我的代码写,到刚刚的地方登陆成功后,会发现首页的name没有值,这里牵涉到vuex的一些知识,由于我也不懂,所以只提供解决方案
打开store文件夹下的user文件,找到getInfo方法
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('Verification failed, please Login again.')
}
const { name, avatar } = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
稍微有点经验的人应该都能发现,这里其实我们的data里的数据跟默认的还是有区别的,我是根据自己业务需求定义的
const { name, avatar } = data
这句代码其实跟我的数据是不匹配的,我的data中存放的是username,id,role,所以这样提取肯定是会有问题的
修改为,其实这里我也不需要role,但是我会需要用户的id,修改后代码如下
const { username, id } = data
然后不难发现下面的两句代码我们也需要修改,这里我不讲原理,只讲实现
commit('SET_NAME', username)
commit('SET_ID', id)
修改完成后还是会有问题,还需要在文件最上方的代码进行修改,就是这一段
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
}
}
看到第三行,我们应该就大概知道这其实就是在赋值,通过commit去传递值,大概就是这样,这里我添加了自己需要的id
SET_ID: (state, id) => {
state.id = id
},
设置完之后,我们找到主页的index文件,小小的修改一下
这样自己可以先试一下,其实name出来了,id还是没有出来,我们就可以继续找原因,然后就发现了...mapGetters
这个不知道是什么东西啊,但是好像在哪里见过
这里我们发现了getters文件,打开看看,豁然开朗
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
}
export default getters
将id加上 id: state => state.user.id
大功告成