美文网首页基础前端
以 NodeJs 和 MongoDB 为基础结合 Vue 制作登

以 NodeJs 和 MongoDB 为基础结合 Vue 制作登

作者: CondorHero | 来源:发表于2019-09-25 18:54 被阅读0次
一、制作后台登录接口
接口和数据库

后台接口功能展示:

接口地址 请求方式 接口作用
/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 &copy; 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 的变量里面。后面就可以直接进行权限判断,甚至是组件判断。

相关文章

网友评论

    本文标题:以 NodeJs 和 MongoDB 为基础结合 Vue 制作登

    本文链接:https://www.haomeiwen.com/subject/phutuctx.html