多人博客管理系统&登录功能

多人博客管理系统

目标

  • 能够知道搭建项目环境的步骤
  • 能够理解模板优化
  • 熟悉登录功能的思路逻辑
  • 知道密码为什么需要加密
  • 知道cookie跟session是什么
  • 能够参照笔记写出登录功能

功能需求

  • 博客内容展示

    • 文章列表页面
      [外链图片转存失败(img-twPJASUX-1568462831538)(images/博客-01.png)]

    • 文章详情页面

      [外链图片转存失败(img-zKIf5sXe-1568462831539)(images/博客-02.png)]

  • 博客管理功能

    • 登录页面

      [外链图片转存失败(img-NcjieJDZ-1568462831544)(images/博客-03.png)]

    • 管理页面

      [外链图片转存失败(img-8wzKRKRq-1568462831546)(images/博客-04.png)]

项目环境搭建

  • 建立项目所需文件夹

    • public 静态资源
    • model 数据库操作
    • route 路由
    • views 模板
  • 初始化项目描述文件

    • npm init -y
  • 下载项目所需要的第三方模块

    • npm install express mongoose art-template express-art-template
  • 创建网站服务器

    //引入 express第三方模块
    const express = require('express');
    //创建web服务器
    const app = express();
    //监听端口
    app.listen(80);
    
  • 构建模块化路由

    • 在 route 文件夹下 创建 admin.js 和 home.js 两个模块的路由文件

    • admin.js

      // 引用expess框架
      const express = require('express');
      // 创建博客展示页面路由
      const admin = express.Router();
      // 将路由对象做为模块成员进行导出
      module.exports = admin;
      
    • home.js

      // 引用expess框架
      const express = require('express');
      // 创建博客展示页面路由
      const home = express.Router();
      // 将路由对象做为模块成员进行导出
      module.exports = home;
      
    • 在入口文件 app.js文件中配置模块化路由

      // 引入路由模块
      const home = require('./route/home');
      const admin = require('./route/admin');
      // 为路由匹配请求路径
      app.use('/home', home);
      app.use('/admin', admin);
      
  • 配置静态资源文件

    • 引入path文件,利用static方法来配置静态资源

      // 处理路径
      const path = require('path');
      // 开放静态资源文件
      app.use(express.static(path.join(__dirname, 'public')))
      
    • 把静态资源的html文件,放到views目录下

  • 构建模板

    // 告诉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')))
    

    注意 : 模板中的相对路径是相对于地址栏中的请求路径的

    所以在模板中引用其他文件中的资源的时候 需要使用服务器的绝对路径 也就是在src 或者 link的路径前加上“/”

    <link rel="stylesheet" href="/admin/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/admin/css/base.css">
    

模板优化

抽离公共部分到单独模板中去

在common文件夹中创建头部公共模板文件

header.art


<div class="header">
	
    <div class="logo fl">
      黑马程序员 <i>ITHEIMAi>
    div>
    
    
    <div class="info">
        <div class="profile dropdown fr">
            <span class="btn dropdown-toggle" data-toggle="dropdown">
				{{userInfo && userInfo.username}}
				<span class="caret">span>
            span>
            <ul class="dropdown-menu">
                <li><a href="user-edit.html">个人资料a>li>
                <li><a href="/admin/logout">退出登录a>li>
            ul>
        div>
    div>
    
div>

创建aside.art


<div class="aside fl">
    <ul class="menu list-unstyled">
        <li>
            <a class="item active" href="user.html">
				<span class="glyphicon glyphicon-user">span>
				用户管理
			a>
        li>
        <li>
            <a class="item" href="article.html">
	  			<span class="glyphicon glyphicon-th-list">span>
	  			文章管理
	  		a>
        li>
    ul>
    <div class="cprt">
        Powered by <a href="http://www.itheima.com/" target="_blank">黑马程序员a>
    div>
div>

在use.art中引入

<body>
	
	
    {{include './common/header.art'}}
    
    
    <div class="content">
    	
        {{include './common/aside.art'}}
        
        ...
    div>
body>

抽取layout.art 骨架模板,每个页面可能还需要引入其他样式或者是javascript代码,所以我们需要在骨架模板中设置坑,后续每个页面想添加了通过 block 引入进来就可以了


<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>Blog - Content Managertitle>
    <link rel="stylesheet" href="/admin/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/admin/css/base.css">
    
    {{block 'link'}}{{/block}}
head>

<body>
    
	{{block 'main'}} {{/block}}
	<script src="/admin/lib/jquery/dist/jquery.min.js">script>
	<script src="/admin/lib/bootstrap/js/bootstrap.min.js">script>
    <script src="/admin/js/common.js">script>
    
	{{block 'script'}} {{/block}}
body>

html>

后续页面需要通过 extend来进行引入,这里用一个 user.art进行实例,后续页面参考即可


{{extend './common/layout.art'}}

{{block 'main'}} 
    
    {{include './common/header.art'}}
    
    <div class="content">
    	{{include './common/aside.art'}}
      ...
    div>
    
    
    <div class="modal fade confirm-modal">
      ...
    div>
{{/block}}

登录功能

用户的数据存储在数据库中,所以我们需要去链接数据库,创建用户的集合

连接数据库

model文件夹下,创建connect.js 文件,里面写连接数据库代码

// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost/blog', {useNewUrlParser: true })
	.then(() => console.log('数据库连接成功'))
	.catch(() => console.log('数据库连接失败'))

创建用户集合

model文件夹下,创建user.js 文件,里面创建用户规则

// 创建用户集合
// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 创建用户集合规则
const userSchema = new mongoose.Schema({
	username: {
		type: String,
		required: true,
		minlength: 2,
		maxlength: 20
	},
	email: {
		type: String,
		// 保证邮箱地址在插入数据库时不重复
		unique: true,
		required: true
	},
	password: {
		type: String,
		required: true
	},
	// admin 超级管理员
	// normal 普通用户
	role: {
		type: String,
		required: true
	},
	// 0 启用状态
	// 1 禁用状态
	state: {
		type: Number,
		default: 0
	}
});

// 创建集合
const User = mongoose.model('User', userSchema);

// 将用户集合做为模块成员进行导出,
//module.exports = {
//	User:User
//}
//在es6中,如果属性名跟属性值都相等,可以简写
module.exports = {
	User
}

初始化一些数据存入数据库

为了方便我们实现登录功能我们先初始化一个用户;这段代码用来测试 执行过一次 创建过admin之后就应该删除或者注释掉

User.create({
  username:'itheima',
  email:'[email protected]',
  password:'123456', 
  role:'admin',
  state:0
}).then(()=>{
  console.log('用户创建成功')
}).catch(()=>{
  console.log('用户创建失败')
})

为登录表单设置相应属性

需要设置请求地址,请求方式,添加name属性


<form action="/admin/login" method="post" id="loginForm">
    <div class="form-group">
        <label>邮件label>
        
        <input name="email" type="email" class="form-control" placeholder="请输入邮件地址">
    div>
    <div class="form-group">
        <label>密码label>
        <input name="password" type="password" class="form-control" placeholder="请输入密码">
    div>
    <button type="submit" class="btn btn-primary">登录button>
form>

客户端验证用户是否填写了登录表单

<script src="/admin/js/common.js"></script>
<script type="text/javascript">
        // 为表单添加提交事件 默认是true
        $('#loginForm').on('submit', function () {
            // 获取到表单中用户输入的内容  $().serializeToJson 是jQuery提供的方法,返回的值是一个数组,如果用户在邮箱里面填写了 [email protected] 那么获取到的值: [{'email':'[email protected]'}]
            var result = serializeToJson($(this))
            // 如果用户没有输入邮件地址的话
            if (result.email.trim().length == 0) {
                alert('请输入邮件地址');
                // 阻止程序向下执行 不会进行提交
                return false;
            }
            // 如果用户没有输入密码
            if (result.password.trim().length == 0) {
                alert('请输入密码')
                // 阻止程序向下执行 不会进行提交
                return false;
            }
        });
    </script>

上面实例代码的 serializeToJson() 是自己封装的一个函数,这个属于是一个公共的代码,所以放在了 public/admin/js/common.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;
}

服务器端添加登录路由

post请求我们需要依赖 body-parser 模块,利用 npm install body-parser 下载

在app.js 里面引入 body-parser模块,绑定路由

// 引入body-parser模块 用来处理post请求参数
const bodyPaser = require('body-parser');
// 处理post请求参数
app.use(bodyPaser.urlencoded({ extended: false }));

服务器这边还需要对账号密码进行校验

// 添加实现登录请求的路由
admin.post('/login', (req,res)=>{ 
	// 接收请求参数
	const {email, password} = req.body;
	// 如果用户没有输入邮件地址
	// if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).send('

邮件地址或者密码错误

');
if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).render('admin/error', {msg: '邮件地址或者密码错误'});// });

代码到这一步还只完成了校验是否为空,后面需要通过用户名跟密码与数据库中进行比对

//用户集合定义在user.js中,我们需要进行导入
const { User } = require('../model/user')

admin.post('/login', async (req,res)=>{ 
    // 接收请求参数
	const {email, password} = req.body;
	...
    //数据库的查询是异步的,所以我们可以通过 async 配合 await来得到查询的结果
	let user = await User.findOne({email});
	// 查询到了用户
	if (user) {
		// 将客户端传递过来的密码和用户信息中的密码进行比对
		// true 比对成功
		// false 对比失败
		
		// 如果密码比对成功
		if ( password = user.password ) {
			// 登录成功
			res.send('登录成功');
		} else {
			// 没有查询到用户
			res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
		}
	} else {
		// 没有查询到用户
		res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
	}
 });

密码加密-哈希密码加密

账号的密码是比较私密的,如果是明文进行传递,那么很容易被别人盗取,所以我们需要对密码进行一个加密处理,这里我们使用一个第三方的模块 bcrypt

bcrypt 依赖的其他环境

  1. python 2.x
  2. node-gyp;npm install -g node-gyp
  3. windows-build-tools;npm install --global --production windows-build-tools

实例demo

// 导入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();

把加密处理引入到我们项目中

在保存到数据库之前,需要把密码进行加密,需要在user.js里面去处理

async function createUser() {
    const salt = await bcrypt.genSalt(10);
    const pass = await bcrypt.hash('123456', salt);
    const user = await User.create({
        username: 'iteheima',
        email: '[email protected]',
        password: pass,
        role: 'admin',
        state: 0
    });
}

由于我们把数据库中的密码进行了加密,所以我们不能直接用用户输入的密码与数据库的密码比对,需要把用户传递过来的密码进行加密,然后再和数据库中的比对

...
if (user) {
        console.log(password);
        // 将客户端传递过来的密码和用户信息中的密码进行比对
        // true 比对成功
        // false 对比失败
        let isValid = await bcrypt.compare(password, user.password);
        // 如果密码比对成功
        if (isValid) {
            // 登录成功
            // 将用户名存储在请求对象中
            req.username = user.username;
            // 重定向到用户列表页面
            res.redirect('/admin/user');
        } else {
            // 没有查询到用户
            res.status(400).render('admin/error', { msg: '邮箱地址或者密码错误' })
        }
    }
    ...

登录状态的保持-cookie&session

cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据。

  • cookie中的数据是以域名的形式进行区分的。
  • cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
  • cookie中的数据会随着请求被自动发送到服务器端。

[外链图片转存失败(img-CWaxCiy9-1568462831548)(images/博客-05.png)]

session:实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识

[外链图片转存失败(img-lYSh0sUg-1568462831551)(images/博客-06.png)]

实现session功能需要借助 express-session

语法:

//首先需要下载 express-session: npm install express-session
const session = require('express-session');
app.use(session({ secret: 'secret key' }));

app.js 中引入

// 导入express-session模块
const session = require('express-session');
// 配置session
app.use(session({
    secret: 'secret key',
    saveUninitialized: false,
    cookie: {
        maxAge: 24 * 60 * 60 * 1000
    }
}));

login的路由中,当用户登录成功后,需要把用户名保存在session中

...
if (user) {
        console.log(password);
        // 将客户端传递过来的密码和用户信息中的密码进行比对
        // true 比对成功
        // false 对比失败
        let isValid = await bcrypt.compare(password, user.password);
        // 如果密码比对成功
        if (isValid) {
            // 登录成功
            // 将用户名存储在请求对象中,当我们引入了express-session模块后,我们可以通过req对象得到session对象
            req.session.username = user.username;
            // 重定向到用户列表页面
            res.redirect('/admin/user');
        } else {
            // 没有查询到用户
            res.status(400).render('admin/error', { msg: '邮箱地址或者密码错误' })
        }
    }
    ...

在用户页面从session中获取数据

// 创建用户列表路由
admin.get('/user',(req,res)=>{
  res.render('admin/user',{
    msg:req.session.username
  })
})

你可能感兴趣的:(多人博客管理系统&登录功能)