(二)项目功能的实现——用户管理

BLOG -- 源码目录
 └── middleware 中间件
     └──loginGuard.js 登录拦截
 └── model -- 数据库操作
     ├── connect.js --数据库连接
     └── user.js --用户管理
 └── public -- 静态资源
     └── admin --博客管理页面静态资源
           └── common.js --客户端验证登录表单
└──  route -- 路由
        └──  admin --博客管理页面
               ├──loginPage.js 登录页面渲染
               ├──logout.js 退出页面功能实现
               ├──userPage.js 用户页面渲染
               └── login.js 登录页面功能实现

        └── admin.js --博客管理页面路由
 └── views -- 模板
      └── admin --博客管理页面art模板
           └── common 公共模块
                  └── header.art 公共头部模块
           ├── login.art --登录页面
           └── error.art --错误页面
 ├── app.js -- 创建网站服务
 └──hash.js -- 对bcrypt的测试代码

1、connect.js

  • 链接数据库: 引入mongoose、链接数据库
// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 导入config模块
const config = require('config');
console.log(config.get('db.host'))
// 连接数据库
mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`, {useNewUrlParser: true })
    .then(() => console.log('数据库连接成功'))
    .catch(() => console.log('数据库连接失败'))

2.app.js

  • 数据库链接:require connect
  • require('./model/user');·// 创建用户,建完之后需要注释掉
  • 服务器端接收请求参数,验证用户是否填写了登录表单:

    • 下载好第三方模块后重新启动服务器 npm install body-parser
    • app.js 引入body-parser模块 用来处理post请求参数
  • 显示登录状态,需要保存登录的状态:导入express-session模块,npm install express-session拦截请求 判断用户登录状态,
  • 拦截请求 判断用户登录状态,require loginGuard
// 引用expess框架
const express = require('express');
// 处理路径
const path = require('path');
// 引入body-parser模块 用来处理post请求参数
const bodyPaser = require('body-parser');
// 导入express-session模块
const session = require('express-session');
// 创建网站服务器
const app = express();
// 数据库连接
require('./model/connect');
// 处理post请求参数
app.use(bodyPaser.urlencoded({extended: false}));
// 告诉express框架模板所在的位置
app.set('views', path.join(__dirname, 'views'));
// 告诉express框架模板的默认后缀是什么
app.set('view engine', 'art');
// 当渲染后缀为art的模板时 所使用的模板引擎是什么
app.engine('art', require('express-art-template'));
// 开放静态资源文件
app.use(express.static(path.join(__dirname, 'public')));
// 引入路由模块
const home = require('./route/home');
const admin = require('./route/admin');
// 拦截请求 判断用户登录状态
app.use('/admin', require('./middleware/loginGuard'));
// 为路由匹配请求路径
app.use('/home', home);
app.use('/admin', admin);
// 监听端口
app.listen(80);
console.log('网站服务器启动成功, 请访问localhost')

3.user.js

  • 创建用户集合,初始化用户:集合创建后记得删除,否则会重复创建报错,将用户集合做为模块成员进行导出
  • 使用了bcrypt加密方式,导入bcrypt模块,
// 创建用户集合
// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 导入bcrypt
const bcrypt = require('bcrypt');
// 创建用户集合规则
const userSchema = new mongoose.Schema({
    username: {
        ···
    },
    email: {
        ···
    }
···
});

// 创建集合
async function createUser () {
    const salt = await bcrypt.genSalt(10);
    const pass = await bcrypt.hash('123456', salt);
    const user = await User.create({
        username: 'xx',
        email: 'xx',
        password: pass,
        role: 'admin',
        state: 0
    });
}
// createUser();
// 将用户集合做为模块成员进行导出
module.exports = {
    User
}

4.login.art

  • 为登录表单项设置请求地址、请求方式以及表单项name属性,为方便获取表单,为表单添加id值
  • 如果邮件和密码其中一项没有输入,阻止表单提交
<body>
<div class="login">
                <form action="xx" method="post" id="loginForm">
···
 <input name="email" type="email" class="xx" placeholder="请输入邮件地址">
···
<script type="text/javascript">
        // 为表单添加提交事件
        $('#loginForm').on('submit', function () {
            // 获取到表单中用户输入的内容
            var result = serializeToJson($(this))
            // 如果用户没有输入邮件地址的话
            if (result.email.trim().length == 0) {
                alert('请输入邮件地址');
                // 阻止程序向下执行
                return false;
            }
            // 如果用户没有输入密码
            if (result.password.trim().length == 0) {
                alert('请输入密码')
                // 阻止程序向下执行
                return false;
            }
        });
    </script>
</body>

5.common.js

  • 当用户点击登录按钮时,客户端验证用户是否填写了登录表单,并添加到layout.js中,使得每个页面都可以访问到
function serializeToJson(form) {
    var result = {};
    // [{name: 'email', value: '用户输入的内容'}]
    var f =  form.serializeArray();  
    f.forEach(function (item) {
        // result.email
        result[item.name] = item.value;
    });
    return result;
}

layout.js

<!DOCTYPE html>
<html lang="en">
<head>
 ···
 {{block 'link'}}{{/block}}
</head>
<body>
 {{block 'main'}} {{/block}}
 ···
 <script src="/admin/js/common.js"></script>
 {{block 'script'}} {{/block}}
</body>
</html>

6.login.js

  • 实现登录功能,如果其中一项没有输入(trim().length == 0),为客户端做出响应,阻止程序向下执行
  • 根据邮箱地址查询用户信息(导入用户集合构造函数),如果用户不存在(用findOne查询),为客户端做出响应,阻止程序向下执行
  • 如果用户存在,将用户名和密码(使用了bcrypt加密)进行比对(bcrypt.compare(password, user.password)),比对成功,用户登录成功,比对失败,用户登录失败

    • 密码加密处理 bcrypt

    bcrypt依赖的其他环境

    1.安装python 并把python设置到环境变量中
    2.node-gyp
    npm install node-gyp -g
    3.windows-build-tools

    Python 2版本:npm install --global --production windows-build-tools
    Python其它版本:https://blog.csdn.net/weixin_...
  • 对用户角色进行判断, 将用户角色存储在session对象中,如果不是admin至登录页面,如果是则进入用户列表页面
  • req.app.locals,req可以获得app对象,选择其中的locals属性,可以不用多次渲染
  • render('admin/error')跳转失败则渲染error页面
// 导入用户集合构造函数
const { User } = require('../../model/user');
const bcrypt = require('bcrypt');

module.exports = async (req, res) => {
    // 接收请求参数
    const {email, password} = req.body;
    // 如果用户没有输入邮件地址
    // if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).send('<h4>邮件地址或者密码错误</h4>');
    if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).render('admin/error', {msg: '邮件地址或者密码错误'});
    // 根据邮箱地址查询用户信息
    // 如果查询到了用户 user变量的值是对象类型 对象中存储的是用户信息
    // 如果没有查询到用户 user变量为空
    let user = await User.findOne({email});
    // 查询到了用户
    if (user) {
        // 将客户端传递过来的密码和用户信息中的密码进行比对
        // true 比对成功
        // false 对比失败
        let isValid = await bcrypt.compare(password, user.password);
        // 如果密码比对成功
        if ( isValid ) {
            // 登录成功
            // 将用户名存储在请求对象中
            req.session.username = user.username;
            // 将用户角色存储在session对象中
            req.session.role = user.role;
            // res.send('登录成功');
            req.app.locals.userInfo = user;
            // 对用户的角色进行判断
            if (user.role == 'admin') {
                // 重定向到用户列表页面
                res.redirect('/admin/user');
            } else {
                // 重定向到博客首页
                res.redirect('/home/');
            }
            
        } else {
            // 没有查询到用户
            res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
        }
    } else {
        // 没有查询到用户
        res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
    }
}

7.header.art

  • {{userInfo && userInfo.username}}判断userInfo是否存在,防止因为不存在而出现的报错
<!-- 头部 -->
<div class="header">
    <!-- 用户信息 -->
    <div class="info">
        <div class="profile dropdown fr">
            <span class="btn dropdown-toggle" data-toggle="dropdown">
                {{userInfo && userInfo.username}}
                <span class="caret"></span>
            </span>
        </div>
    </div>
    <!-- /用户信息 -->
</div>

8.loginGuard.js

  • 实现登录拦截的功能:判断除了login以外的页面且已经属于登录状态(req.url != '/login' && !req.session.username)再判断用户属性是普通用户还是管理员
onst guard = (req, res, next) => {
    // 判断用户访问的是否是登录页面
    // 判断用户的登录状态
    // 如果用户是登录的 将请求放行
    // 如果用户不是登录的 将请求重定向到登录页面
    if (req.url != '/login' && !req.session.username) {
        res.redirect('/admin/login');
    } else {
        // 如果用户是登录状态 并且是一个普通用户
        if (req.session.role == 'normal') {
            // 让它跳转到博客首页 阻止程序向下执行
            return res.redirect('/home/')
        }
        // 用户是登录状态 将请求放行
        next();
    }
}

module.exports = guard;

9.hash.js

// 导入bcrypt
const bcrypt = require('bcrypt');


async function run () {
    // 生成随机字符串
    // genSalt方法接收一个数值作为参数
    // 数值越大 生成的随机字符串复杂度越高
    // 数值越小 生成的随机字符串复杂度越低
    // 默认值是 10
    // 返回生成的随机字符串
    const salt = await bcrypt.genSalt(10);
    // 对密码进行加密
    // 1. 要进行加密的明文
    // 2. 随机字符串
    // 返回值是加密后的密码
    const result = await bcrypt.hash('123456', salt);
    console.log(salt);
    console.log(result);
}

run();

10.loginPage.js

  • 模块分离功能:实现登录页面的渲染
  • 通过模块化来实现代码的复用,最后要把路由对象作为模块成员进行导出
  • 类似的还有logout.js userPage.js login.js
module.exports = (req, res) => {
    res.render('admin/login');
}

11.admin.js

  • 通过require把到处去的模块成员接入
  • admin同样作为模块成员进行导出,让app.js接入
// 引用expess框架
const express = require('express');
// 创建博客展示页面路由
const admin = express.Router();
// 渲染登录页面
admin.get('/login', require('./admin/loginPage'));

// 实现登录功能
admin.post('/login', require('./admin/login'));

// 创建用户列表路由
admin.get('/user', require('./admin/userPage'));

// 实现退出功能
admin.get('/logout', require('./admin/logout'));
// 将路由对象做为模块成员进行导出
module.exports = admin;

12、error.art

  • 优化页面:错误页面三秒后返回登录页面,基于layout骨架
{{extend './common/layout.art'}}

{{block 'main'}}
    <p class="bg-danger error">{{msg}}</p>
{{/block}}

{{block 'script'}}
    <script type="text/javascript">
        setTimeout(function () {
            location.href = '/admin/login';
        }, 3000)
    </script>
{{/block}}

日月彗心
4 声望0 粉丝

人生唯一确定的就是不确定的人生,人生唯一恐惧的就是恐惧本身