文章目录
博客项目
1. 项目环境搭建
1.1 项目介绍
多人博客管理系统
- 博客内容展示
- 博客管理功能
1.2 案例初始化
1.建立项目所需文件夹
- public 静态资源
- model 数据库操作
- route 路由
- views 模板
2.初始化项目描述文件
- npm init -y
3.下载项目所需第三方模块
- npm install express mongoose art-template express-art-template
4.创建网站服务器
5.构建模块化路由
6.构建博客管理页面模板
静态资源的外链文件是由浏览器解析的,其中的相对路径不是相对于当前文件,而是相对浏览器中的请求路径
改为:
其他类似
模板的路径是由模板引擎解析的,其中的相对路径是相对当前文件
主要代码
app.js
// 引用express框架
const express = require('express');
// 处理路径
const path = require('path');
// 创建网站服务器
const app=express();
// 告诉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('/home',home);
app.use('/admin',admin);
// 监听端口 开发的时候一般监听3000端口
app.listen(80);
console.log('网站服务器启动成功,请访问localhost');
home.js
// 引用express框架
const express = require('express');
// 创建博客展示页面路由
const home = express.Router();
home.get('/', (req, res) => {
res.send('欢迎来到博客首页');
});
// 将路由对象做为模块成员进行导出
module.exports = home;
admin.js
// 引用express框架
const express = require('express');
// 创建博客展示页面路由
const admin = express.Router();
admin.get('/login', (req, res) => {
res.render('admin/login');
});
// 创建用户列表路由
admin.get('/user',(req, res) => {
res.render('admin/user');
})
// 将路由对象做为模块成员进行导出
module.exports = admin;
layout.art
<!DOCTYPE html>
<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 Manager</title>
<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>
{{block 'script'}} {{/block}}
</body>
</html>
header.art
<!-- 头部 -->
<div class="header">
<!-- 网站标志 -->
<div class="logo fl">
黑马程序员 <i>ITHEIMA</i>
</div>
<!-- /网站标志 -->
<!-- 用户信息 -->
<div class="info">
<div class="profile dropdown fr">
<span class="btn dropdown-toggle" data-toggle="dropdown">
admin
<span class="caret"></span>
</span>
<ul class="dropdown-menu">
<li><a href="user-edit.html">个人资料</a></li>
<li><a href="#">退出登录</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>
<!-- 侧边栏 -->
article-edit.art
{{extend './common/layout.art'}}
{{block 'main'}}
<!-- 头部 -->
<!-- 子模板的相对路径相对的就是当前文件 因为它是由模板引擎解析的,而不是浏览器 -->
{{include './common/header.art'}}
<!-- /头部 -->
<!-- 主体内容 -->
<div class="content">
<!-- 侧边栏 -->
<!-- 子模板的相对路径相对的就是当前文件 因为它是由模板引擎解析的,而不是浏览器 -->
{{include './common/aside.art'}}
<!-- /侧边栏 -->
<div class="main">
<!-- 分类标题 -->
<div class="title">
<h4>5b9a716cb2d2bf17706bcc0a</h4>
</div>
<!-- /分类标题 -->
<form class="form-container">
<div class="form-group">
<label>标题</label>
<input type="text" class="form-control" placeholder="请输入文章标题">
</div>
<div class="form-group">
<label>作者</label>
<input type="text" class="form-control" readonly>
</div>
<div class="form-group">
<label>发布时间</label>
<input type="date" class="form-control">
</div>
<div class="form-group">
<label for="exampleInputFile">文章封面</label>
<input type="file">
<div class="thumbnail-waper">
<img class="img-thumbnail" src="">
</div>
</div>
<div class="form-group">
<label>内容</label>
<textarea class="form-control" id="editor"></textarea>
</div>
<div class="buttons">
<input type="submit" class="btn btn-primary">
</div>
</form>
</div>
</div>
<!-- /主体内容 -->
{{/block}}
{{block 'script'}}
<script src="/admin/lib/ckeditor5/ckeditor.js"></script>
<script type="/admin/text/javascript">
let editor;
ClassicEditor
.create( document.querySelector('#editor'))
.then(newEditor => {
editor = newEditor;
})
.catch( error => {
console.error( error );
});
// 获取数据
// const editorData = editor.getData();
</script>
{{/block}}
其余类似
2. 项目功能实现
2.1 登录
- 创建用户集合,初始化用户
- 连接数据库
- 创建用户集合
- 初始化用户
- 为登录表单项设置请求地址、请求方式以及表单项name属性
- 当用户点击登录按钮时,客户端验证用户是否填写了登录表单
- 如果其中一项没有输入,阻止表单提交
- 服务器端接收请求参数,验证用户是否填写了登录表单
- 如果其中一项没有输入,为客户端做出响应,阻止程序向下执行
- 根据邮箱地址查询用户信息
- 如果用户不存在,为客户端做出响应,阻止程序向下执行
- 如果用户存在,将用户名和密码进行比对
- 比对成功,用户登录成功
- 比对失败,用户登录失败
- 保存登录状态
- 密码加密处理
2.2 新增用户
- 为用户列表页面的新增用户按钮添加链接
- 添加一个连接对应的路由,在路由处理函数中渲染新增用户模板
- 为新增用户表单指定请求地址、请求方式、为表单项添加name属性
- 增加实现添加用户的功能路由
- 接收到客户端传递过来的请求参数
- 对请求参数的格式进行验证
- 验证当前要注册的邮箱地址是否已经注册过
- 对密码进行加密处理
- 将用户信息添加到数据库中
- 重定向页面到用户列表页面
2.3 数据分页
当数据库中的数据非常多是,数据需要分批次显示,这时就需要用到数据分页功能。
分页功能核心要素:
- 当前页,用户通过点击上一页或者下一页或者页码产生,客户端通过get参数方式传递到服务器端
- 总页数,根据总页数判断当前页是否为最后一页,根据判断结果做响应操作
总页数:Math.ceil(总数据条数 / 每页显示数据条数)
limit(2) // limit 限制查询数量 传入每页显示的数据数量
skip(1) // skip 跳过多少条数据 传入显示数据的开始位置
数据开始查询位置=(当前页-1)* 每页显示的数据条数
2.4 用户信息修改
- 将要修改的用户ID传递到服务器端
- 建立用户信息修改功能对应的路由
- 接收客户端表单传递过来的请求参数
- 根据id查询用户信息,并将客户端传递过来的密码和数据库中的密码进行比对
- 如果比对失败,对客户端做出响应
- 如果密码对比成功,将用户信息更新到数据库中
2.5 用户信息删除
- 在确认删除框中添加隐藏域用以存储要删除用户的ID值
- 为删除按钮添自定义属性用以存储要删除用户的ID值
- 为删除按钮添加点击事件,在点击事件处理函数中获取自定义属性中存储的ID值并将ID值存储在表单的隐藏域中
- 为删除表单添加提交地址以及提交方式
- 在服务器端建立删除功能路由
- 接收客户端传递过来的id参数
- 根据id删除用户
2.6 开发环境与生产环境
什么是开发环境与生产环境
环境,就是指项目运行的地方,当项目处于开发阶段,项目运行在开发人员的电脑上,项目所处的环境就是开发环境。当项目开发完成以后,要将项目放到真实的网站服务器电脑中运行,项目所处的环境就是生产环境。
为什么要区分开发环境与生产环境
因为在不同的环境中,项目的配置是不一样的,需要在项目代码中判断当前项目运行的环境,根据不同的环境应用不同的项目配置。
如何区分开发环境与生产环境
通过电脑操作系统中的系统环境变量区分当前是开发环境还是生产环境。
if (process.env.NODE_ENV == 'development') {
// 开发环境
} else {
// 生产环境
}
2.7 第三方模块config
作用:允许开发人员将不同运行环境下的应用配置信息抽离到单独的文件中,模块内部自动判断当前应用的运行环境,
并读取对应的配置信息,极大提供应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码
中修改配置信息
使用步骤
- 使用npm install config命令下载模块
- 在项目的根目录下新建config文件夹
- 在config文件夹下面新建default.json、development.json、production.json文件
- 在项目中通过require方法,将模块进行导入
- 使用模块内部提供的get方法获取配置信息
将敏感配置信息存储在环境变量中
6. 在config文件夹中建立custom-environment-variables.json文件
7. 配置项属性的值填写系统环境变量的名字
8. 项目运行时config模块查找系统环境变量,并读取其值作为当前配置项属于的值
{
"db": {
"pwd": "APP_PWD"
}
}
2.8 文章评论
- 创建评论集合
- 判断用户是否登录,如果用户登录,再允许用户提交评论表单
- 在服务器端创建文章评论功能对应的路由
- 在路由请求处理函数中接收客户端传递过来的评论信息
- 将评论信息存储在评论集合中
- 将页面重定向回文章详情页面
- 在文章详情页面路由中获取文章评论信息并展示在页面中
3. 项目包含的知识点
3.1 密码加密 bcrypt
哈希加密是单程加密方式:1234 => abcd
在加密的密码中加入随机字符串可以增加密码被破解的难度。
// 导入bcrypt模块
const bcrypt = require('bcrypt');
// 生成随机字符串 gen => generate 生成 salt 盐
let salt = await bcrypt.genSalt(10);
// 使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码', salt);
// 密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码');
bcrypt依赖的其他环境
- python 2.x
- node-gyp
npm install node-gyp -g - windows-build-tools
npm install --global --production windows-build-tools
最后npm install bcrypt
注意:
- 安装所有模块时最好以管理员身份运行Windows PowerShell
- 最好每安装完一个模块后重启Windows PowerShell(环境变量貌似改变)
- 在最后一步
npm install bcrypt
时安装太慢,本人挂了TZ才行(原因不明)
3.2 cookie与session
cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据。
- cookie中的数据是以域名的形式进行区分的。
- cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
- cookie中的数据会随着请求被自动发送到服务器端。
session:实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识。
在node.js中需要借助express-session实现session功能。
const session = require('express-session');
app.use(session({ secret: 'secret key' }));
3.3 Joi
JavaScript对象的规则描述语言和验证器。
const Joi = require('joi');
const schema = {
username: Joi.string().alphanum().min(3).max(30).required().error(new Error(‘错误信息’)),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
};
Joi.validate({ username: 'abc', birthyear: 1994 }, schema);
3.4 formidable
作用:解析表单,支持get请求参数,post请求参数、文件上传。
// 引入formidable模块
const formidable = require('formidable');
// 创建表单解析对象
const form = new formidable.IncomingForm();
// 设置文件上传路径
form.uploadDir = "/my/dir";
// 是否保留表单上传文件的扩展名
form.keepExtensions = false;
// 对表单进行解析
form.parse(req, (err, fields, files) => {
// fields 存储普通请求参数
// files 存储上传的文件信息
});
3.5 文件读取 FileReader
var reader = new FileReader();
reader.readAsDataURL('文件');
reader.onload = function () {
console.log(reader.result);
}
3.6 数据分页 mongoose-sex-page
const pagination = require('mongoose-sex-page');
pagination(集合构造函数).page(1) .size(20) .display(8) .exec();
3.7 mongoDB数据库添加账号
- 以系统管理员的方式运行powershell
- 连接数据库 mongo
- 查看数据库 show dbs
- 切换到admin数据库 use admin
- 创建超级管理员账户 db.createUser()
- 切换到blog数据 use blog
- 创建普通账号 db.createUser()
- 卸载mongodb服务
1. 停止服务 net stop mongodb
2. mongod --remove - 创建mongodb服务
mongod --logpath=“C:\Program Files\MongoDB\Server\4.1\log\mongod.log” --dbpath=“C:\Program Files\MongoDB\Server\4.1\data” --install –-auth - 启动mongodb服务 net start mongodb
- 在项目中使用账号连接数据库
mongoose.connect(‘mongodb://user:pass@localhost:port/database’)