博客内容展示
文章列表页面
[外链图片转存失败(img-twPJASUX-1568462831538)(images/博客-01.png)]
文章详情页面
[外链图片转存失败(img-zKIf5sXe-1568462831539)(images/博客-02.png)]
博客管理功能
登录页面
[外链图片转存失败(img-NcjieJDZ-1568462831544)(images/博客-03.png)]
管理页面
[外链图片转存失败(img-8wzKRKRq-1568462831546)(images/博客-04.png)]
建立项目所需文件夹
初始化项目描述文件
下载项目所需要的第三方模块
创建网站服务器
//引入 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 依赖的其他环境
// 导入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:浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据。
[外链图片转存失败(img-CWaxCiy9-1568462831548)(images/博客-05.png)]
session:实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识
[外链图片转存失败(img-lYSh0sUg-1568462831551)(images/博客-06.png)]
语法:
//首先需要下载 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
})
})