一、制作后台登录接口
接口和数据库
后台接口功能展示:
接口地址 | 请求方式 | 接口作用 |
---|---|---|
/login | post | 表单登录 |
login?username=xiaoming&password=123123 | get | 表单get请求登录 |
/list | get | 学生表单数据 |
/logout | get | 退出登录 |
后台连接的数据库里面有两个测试的用户名和密码:
姓名 | 密码 |
---|---|
xiaoming | 123123 |
xiaohong | 123456 |
数据全部存放在 MongoDB 数据库里面,第一步数据库开机:
D:\mongodb>mongod --dbpath D:/mongodb/database
学生表格数据使用的是 mockjs 模拟出来的(包括图片),生成的 100 条,然后我们可以导入数据库:
mongoimport -d erp -c students db.json
导入成功,查看我们的数据库:
> show dbs
admin 0.000GB
cache 0.000GB
config 0.000GB
erp 0.000GB
local 0.000GB
> use erp
switched to db erp
> show collections
students
users
> db.users.find()
{ "_id" : ObjectId("5d22eb1b347914c5993f1d37"), "username" : "xiaoming", "passwo
rd" : "123123", "nickname" : "小明", "avatar" : "", "mobile" : "", "role" : "man
ager" }
{ "_id" : ObjectId("5d22eb1b347914c5993f1d36"), "username" : "xiaohong", "passwo
rd" : "123456", "nickname" : "小红", "avatar" : "", "mobile" : "", "role" : "wor
ker" }
使用的数据库表格:
用到的数据库名 | 数据表(文档) |
---|---|
erp | users和students |
NodeJs 后台演示代码
- session 和 token 的制作
// 应用中间件进行配置
// 使用 session 中间件
app.use(session({
secret: 'keyboard cat', // 可以随便写,是字符串就行,作为服务端生成session的签名
resave: false, //强制保存session,即使session没有变化,默认为true(影响性能),建议设置false
saveUninitialized: true, // 强制未初始化的session存储,默认建议true,主要针对req.session未设置初始值,其他地方的请求是否出现 session_id
name:"session_id", //保存在本地cookie的一个名字,默认connect.sid可以不设置
cookie: {
maxAge: 1000 * 10, // 设置 session 的有效时间,单位毫秒,第一次访问即开始计时
secure: false, // 网址只能由 https 发送
},
rolling:true,//每次请求强行设置cookie,并重置cookie的过期时间,也就是每次访问都会重新计时。
}));
token 使用 crypto 加密技术
var token = crypto.createHash("sha256").update(fields.username + fields.username).digest('hex');
post 登录
// //登陆
app.post("/login", (req, res) => {
//处理post请求
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
console.log(fields.username,fields.password)
//判定是否正确登陆
User.find({
"username": fields.username,
"password": fields.password
}).exec((err, docs) => {
if (docs.length) {
//登陆成功
req.session.login = true;
req.session.username = fields.username;
var token = crypto.createHash("sha256").update(fields.username + fields.username).digest('hex');
res.json({
"result": 1,
"token": token
});
} else {
res.json({
"result": -1
});
}
});
});
});
- 退出登录
// 退出登录
app.get('/logout', function(req, res) {
// 第一种cookie过期时间为0
req.session.cookie.maxAge = 0;
res.json({
"result": -1
});
});
- 表格分页对应的核心后台代码
// 登录成功返回的数据的数据
app.get('/list', (req, res) => {
if (!req.session.login) {
res.status(401).send("请登录");
return;
}
var {limit,skip} = url.parse(req.url,true).query;
Student.find().skip(Number(skip)).limit(Number(limit)).exec((err,docs)=>{
if (docs.length) {
res.json(docs);
}
});
});
app.js 后台对应的源码见最后:
二、使用 iview 搭建一个基本的项目框架
后台我没有配置跨域,所以我们自己使用 webpack.config.js 配置代理跨域。这样就能请求后天的数据了。webpack.config.js 配置代理跨域:
// 代理跨域
devServer: {
// 此处可以自定义访问端口号
port : 8080,
proxy: {
'/api': {
target: 'http://192.168.2.167:250',
pathRewrite: {'^/api' : ''}
}
}
}
初步 iview 搭配的代码
初步 iview 搭配的代码
项目目录结构:
路由文件 router.js:
// 引入组件
import Index from "../components/Index.vue";
import Login from "../components/Login.vue";
import Students from "../components/Students.vue";
import Teacher from "../components/Teacher.vue";
//
export default [{
// 首页重定向
path: "/",
redirect:"/index"
},{
// 首页
path: "/index",
component: Index,
meta:{
chinese:"Index"
}
}, {
// 注册页
path: "/Login",
component: Login,
meta:{
chinese:"Login"
}
}, {
// 学生页
path: "/students",
component: Students,
meta:{
chinese:"Students"
}
}, {
// 老师页
path: "/teacher",
component: Teacher,
meta:{
chinese:"Teacher"
}
}, ];
登录注册 login.vue
<template>
<div>
<h1>我是登录页面</h1>
<Form ref="formCustom" :model="formCustom" :rules="ruleCustom" :label-width="80">
<Row>
<Col span="6">
<FormItem label="用户名" prop="username">
<Input type="text" v-model="formCustom.username"></Input>
</FormItem>
</Col>
</Row>
<Row>
<Col span="6">
<FormItem label="密码" prop="password">
<Input type="password" v-model="formCustom.password"></Input>
</FormItem>
</Col>
</Row>
<Row>
<Col span="6">
<FormItem label="确认密码" prop="passwordCheck">
<Input type="password" v-model="formCustom.passwordCheck"></Input>
</FormItem>
</Col>
</Row>
<FormItem>
<Button type="primary" @click="handleSubmit('formCustom')">Submit</Button>
<Button @click="handleReset('formCustom')" style="margin-left: 8px">Reset</Button>
</FormItem>
</Form>
</div>
</template>
<script>
import axios from "axios";
export default {
data () {
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please enter your password'));
} else {
callback();
}
};
const username = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please enter your username'));
} else {
callback();
}
};
const validatePassCheck = (rule, value, callback) => {
if (value === '') {
callback(new Error('Please enter your password again'));
} else if (value !== this.formCustom.password) {
callback(new Error('The two input passwords do not match!'));
} else {
callback();
}
};
return {
formCustom: {
password: '',
passwordCheck: '',
username: '',
},
ruleCustom: {
password: [
{ validator: validatePass, trigger: 'blur' }
],
passwordCheck: [
{ validator: validatePassCheck, trigger: 'blur' }
],
username: [
{ validator: username, trigger: 'blur' }
]
}
}
},
methods: {
handleSubmit (name) {
this.$refs[name].validate((valid) => {
console.log(this,this.password)
if (valid) {
// axios({
// method: 'post',
// url: '/api/login',
// data: {"username":this.formCustom.username,"password":this.formCustom.password},
// }).then(data=>{
// if(data.data.result === 1){
// this.$Message.success('Success!');
// }
// });
axios.post("/api/login",{"username":this.formCustom.username,"password":this.formCustom.password}).then(data=>{
if(data.data.result === 1){
this.$Message.success('Success!');
}
});
} else {
this.$Message.error('Fail!');
}
})
},
handleReset (name) {
this.$refs[name].resetFields();
}
}
}
</script>
App.vue 主文件:
<template>
<div class="layout">
<Layout>
<Header>
<Menu mode="horizontal" theme="dark" active-name="1">
<div class="layout-logo"></div>
<div class="layout-nav1">
<MenuItem name="1">
<Icon type="ios-analytics"></Icon>
<router-link to="/index" :class="{
'cur':true,
'default':$router.currentRoute.meta.chinese == 'Index'
}">首页</router-link>
</MenuItem>
<MenuItem name="2">
<Icon type="ios-navigate"></Icon>
<router-link to="/students" :class="{
'cur':true,
'default':$router.currentRoute.meta.chinese == 'Students'
}">学生信息</router-link>
</MenuItem>
<MenuItem name="3">
<Icon type="ios-keypad"></Icon>
<router-link to="/teacher" :class="{
'cur':true,
'default':$router.currentRoute.meta.chinese == 'Teacher'
}">教师信息</router-link>
</MenuItem>
<MenuItem name="4">
<Icon type="ios-analytics"></Icon>
<span @click="layout" :class="{
'cur':true
}">退出登录</span>
</MenuItem>
<MenuItem name="5">
<Icon type="ios-analytics"></Icon>
<router-link to="/login" :class="{
'cur':true,
'default':$router.currentRoute.meta.chinese == 'Login'
}">登录</router-link>
</MenuItem>
</div>
</Menu>
</Header>
<Content :style="{padding: '0 50px'}">
<Breadcrumb :style="{margin: '20px 0'}">
<BreadcrumbItem>Home</BreadcrumbItem>
<BreadcrumbItem>Components</BreadcrumbItem>
<BreadcrumbItem>Layout</BreadcrumbItem>
</Breadcrumb>
<Card>
<div style="min-height: 200px;">
<router-view></router-view>
</div>
</Card>
</Content>
<Footer class="layout-footer-center">2011-2016 © TalkingData</Footer>
</Layout>
</div>
</template>
<script>
import axios from "axios";
export default {
methods:{
async layout(){
// this.$router.push("index");
const {result} = await axios.get("api/logout").then(data=>data.data);
if(result===-1){
this.$Message.success({
content: "成功退出登录!",
duration: 10
});
}
this.checkGroup = false;
}
}
}
</script>
<style scoped>
.cur{
text-decoration: none;
color:rgba(255,255,255,.7);
}
.default{
color:orange;
}
.layout-nav1{
width: 600px;
margin: 0 auto;
margin-right: 20px;
}
</style>
学生表格 student.vue
<template>
<div>
<div class="box">
<Table
stripe
border
:columns="columns"
:data="checkGroup"
/>
<Page
:total="total"
:current="page"
@on-change="changePage"
/>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
props:["cleardata"],
data(){
return {
total:100,
page:1,
skip:10,
checkGroup:[],
columns : [
{title:"序号",key:"id",render: (h, params) => {
// 附赠的render可以替换前面的内容
// 列内容全部在params的row里面
return h('div', [
h('i', params.row.id)
]);
} },
{title:"头像",key:"avatar" ,render(h,params){
return h('div', {style:{width:'50px',height:'50px',backgroundSize:"contain",backgroundImage:`url(${params.row.avatar})`,margin:"5px auto"}},"")
}},
// 表格内容默认左对齐,align:"center"居中
{title:"姓名",key:"name" },
// sortable: true开启字段排序
{title:"年龄",key:"age",sortable: true },
{ title:"共青团员",key:"member"},
{title:"省份",key:"province" },
{title:"性别",key:"sex" },
{title:"教育情况",key:"education" },
{title:"民族",key:"national"},
],
}
},
// 组件创建前进行数据初始化,发送Ajax
async beforeCreate(){
const students = await axios.get("api/list?limit=10&skip=10").then(data=>data.data);
this.checkGroup = students;
},
methods:{
async changePage(page){
this.skip = page;
const students = await axios.get(`api/list?limit=10&skip=${this.skip * 10}`).then(data=>data.data);
console.log(students);
this.checkGroup = students;
}
}
}
</script>
<style scoped>
.box{
width: 1000px;
margin:50px auto;
text-align:center;
}
.ivu-page{
margin-top:20px;
}
</style>
<style scoped>
.layout{
border: 1px solid #d7dde4;
background: #f5f7f9;
position: relative;
border-radius: 4px;
overflow: hidden;
}
.layout-logo{
width: 100px;
height: 30px;
background: #5b6270;
border-radius: 3px;
float: left;
position: relative;
top: 15px;
left: 20px;
}
.layout-nav{
width: 420px;
margin: 0 auto;
margin-right: 20px;
}
.layout-footer-center{
text-align: center;
}
</style>
三、登录注册、权限功能(重要)
接下来就是使用 Vue 进行登录注册,和权限验证。业务重要的部分。在上面我们已经配置了两个测试账号。输入测试账号会发生什么那?
- 服务器下发 session
- 同时请求接口会返回一个 token
然后我们登录的逻辑一共要处理的四个事情:
事情 | 作用 |
---|---|
实现登录页面 | 登录功能 |
路由在每次跳转页面都要用/userinfo接口 验证用户是否登录 |
强制跳转到登录页 |
权限访问 | 如果用户没有权限访问它,级别不够,强制跳转到登录页面。如果他没有这个角色,根本不能让他看见效果 |
如果Ajax发现了status 401Unauthorized | 跳转登录 |
-
实现登录页面:就是一个post请求完成。详见 Login 组件。
登录 - 路由跳转发送 Ajax 检查用户登录。
更改 app.js 增加接口验证。
// 切换路由的时候,发送Ajax来验证用户使用正确登录和发送token
app.get("/userinfo" , (req,res)=>{
if(!req.session.login){
res.status(401);
res.json({"result": -1,"info":"你没有登录"});
return;
}
//算token
var suantoken = crypto.createHash("sha256").update(req.session.username + req.session.username).digest('hex');
if(suantoken != url.parse(req.url,true).query.token){
res.json({"result": -1,"info":"token不正确"});
return ;
}
User.find({
"username" : req.session.username
} , (err , docs)=>{
res.json({
username:docs[0].username,
nickname :docs[0].nickname,
avatar : docs[0].avatar,
mobile : docs[0].mobile,
sex : docs[0].sex,
role : docs[0].role
})
});
});
我们输入
http://127.0.0.1:8080/api/login?username=xiaoming&password=123123
登录成功后复制返回的 token
http://127.0.0.1:8080/api/userinfo?token=d855f63edcbc1db6499f76761889f8080333f89bb1d9fbfaccd859ac329497a4
如果 token 查询正确返回这个用户的信息。
{"username":"xiaoming","nickname":"小明","avatar":"","mobile":"","role":"manager"}
路由守卫解决路由跳转发送 Ajax 请求的要求。不用想肯定是全局守卫中的beforeEach()
我的在 main.js文件里:
// 定义路由守卫
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
// 发出Ajax请求,验证用户是否登录
axios.get("/api/userinfo?token=d855f63edcbc1db6499f76761889f8080333f89bb1d9fbfaccd859ac329497a4").then(data=>{
if (!data.data.result) {
next();
} else {
alert(3)
next("login");
}
}).catch(()=>{
next("login")
});
}
});
token 此处可以从 localStorage 里面读取。不演示了。你在这定义一个全局变量绑定到 Vue 的原型上也是可以的。
如果路由是这样设置的:
{
// 注册页
name:"login",
path: "/login",
component: Login,
meta:{
chinese:"Login"
}
}
路由守卫那里拿到路径就可以改为:
// 定义路由守卫
router.beforeEach((to, from, next) => {
if (to.name === 'login') {
next();
} else {
// 发出Ajax请求,验证用户是否登录
axios.get("/api/userinfo?token=d855f63edcbc1db6499f76761889f8080333f89bb1d9fbfaccd859ac329497a4").then(data=>{
if (!data.data.result) {
next();
} else {
next({
'name': 'login'
});
}
}).catch(()=>{
next({
'name': 'login'
});
});
}
});
这就是命名路由的跳转方法,一般命名路由跳转和路径跳转差不多相同,甚至你可以使用 meta,只要能辨识路由就行。
- 如果 Ajax 发现了status 401Unauthorized(未授权的情况)。
很明显是我们的 Ajax 发送错误,只需在 请求错误的时候进行路由跳转:可直接使用
window.location.hash = "#/login"
// 组件创建前进行数据初始化,发送Ajax
async beforeCreate(){
const students = await axios.get("api/list?limit=10&skip=10").then(data=>data.data).catch(()=>{window.location.hash = "#/login"});
this.checkGroup = students;
}
还可以使用返回的 401 状态码跳转:
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
- 权限访问
{ "_id" : ObjectId("5d22eb1b347914c5993f1d37"), "username" : "xiaoming", "passwo
rd" : "123123", "nickname" : "小明", "avatar" : "", "mobile" : "", "role" : "man
ager" }
{ "_id" : ObjectId("5d22eb1b347914c5993f1d36"), "username" : "xiaohong", "passwo
rd" : "123456", "nickname" : "小红", "avatar" : "", "mobile" : "", "role" : "wor
ker" }
权限访问:一个是经理"role" : "manager"
一个是工作者 "role" : "worker"
在路由守卫的地方就可以吧状态存储到 Vuex 的变量里面。后面就可以直接进行权限判断,甚至是组件判断。
网友评论