typora-copy-images-to: media
使用到的技术:node、express、swig、mongoose、模块化设计、中间件(body-parser、cookies)、bootstrap
第三方模块、中间件
- express
- bodyParser
- cookies
- swig: 模板引擎
- mongoose:操作mongodb数据库
- markdown:markdown语法解析生成模块
- jquery
- bootstrap
项目初始化
生成package.json文件
npm init -y
安装上面的插件
npm install express body-parser...
配置、使用模板引擎
配置
const swig = require('swig');
...
app.engine('html', swig.renderFile); // 定义当前应用所使用的模板引擎。 参数1:模板引擎后缀;参数2:用于解析处理模块内容的方法
app.set('views', './views'); // 设置模板文件寸法的目录。 参数1:必须是views;参数2:模板文件存放的目录
app.set('view engine', 'html'); // 注册所使用的模板引擎。 参数1:必须是view engine;参数2:和上面的模板引擎后缀必须一致(html)
// 开发过程中,取消模板缓存,方便调试
swig.setDefaults({cache: false});
使用
app.get('/', function(req, res, next){
// 读取views目录下的指定文件,解析并返回给客户端
res.render('index', {}); // 参数1:模板文件(可以忽略后缀,可以忽略views/);参数2:传递给模板使用的数据
});
静态文件的托管
配置
// 当用户访问的url以 /public 开始,直接返回 path.join(__dirname, 'node_modules') 对应的文件
app.use('/node_modules', express.static(path.join(__dirname, 'node_modules')));
app.use('/public', express.static(path.join(__dirname, 'public')));
加载中间件
body-parser
const bodyParser = require('body-parser');
...
// 设置body-parser
app.use(bodyParser.urlencoded({extended: true}));
app.use( bodyParser.json() );
cookies
// app.js
const Cookies = require('cookie');
...
// 设置cookie
app.use(function(req, res, next){
req.cookies = new Cookies(req, res);
next();
});
// api.js
req.cookies.set( 'userInfo', JSON.stringify(resData.userInfo) ); // 添加cookies
req.cookies.get('userInfo'); // 获取cookies
模块划分
使用app.use()进行模块划分
app.use('/admin', require('./routers/admin'));
app.use('/api', require('./routers/api'));
app.use('/', require('./routers/main'));
路由
前台
-
main模块
路径 方法 get参数 post参数 备注 / get 首页 /view get 内容页 -
api模块
路径 方法 get参数 post参数 备注 / get 首页 /register 用户注册 /login 用户登录 /comment 评论获取 /comment/post 评论提交
后台
-
admin模块
路径 方法 get参数 post参数 备注 / 首页 用户管理 /user 用户列表 分类管理 /category 分类列表 /category/add 分类添加 /category/edit 分类修改 /category/delete 分类删除 文章内容管理 /article 文章列表 /article/add 文章添加 /article/edit 文章修改 /article/delete 文章删除 评论内容管理 /comment 评论列表 /comment/delete 评论删除
数据库连接
启动mongodb数据库服务
将mongodb数据库存放到“D: 00web20workw02_blogdb”目录下
mongod --dbpath=D: 00web20workw02_blogdb
连接数据库
// app.js
const mongoose = require('mongoose');
...
// 连接数据库
mongoose.connect('mongodb://127.0.0.1:27017/blog', {useNewUrlParser: true, useUnifiedTopology: true}, function(err){
if( err ){
console.log('数据库连接失败!');
}else{
console.log('数据库连接成功!');
// 监听http端口
app.listen(5000);
}
});
数据库设计
-
用户表
-
schemas/users.js - 表结构对象
const mongoose = require('mongoose'); // 用户表结构对象 module.exports = new mongoose.Schema({ username: String, password: String });
-
models/User.js - 模型类
const mongoose = require('mongoose'); const userSchema = require('../schemas/users'); // 创建用户表的模型类 module.exports = mongoose.model('User', userSchema);
-
-
分类表
-
schemas/categories.js - 表结构对象
-
model/Category.js - 模型类
-
用户
用户注册
-
前端
- 注册页面渲染 - 用户注册、登录面板切换
- 注册功能实现 - ajax提交到api.js - /api/user/register
-
后端
- 注册功能(错误码:1xx)
- 前端验证
- 1、用户名不能为空
- 2、密码不能为空
- 3、两次密码必须一致
- 数据库验证
- 1、用户是否已经存在
- 注册成功
- 保存用户注册信息到数据库中
- 前端验证
- 注册功能(错误码:1xx)
// index.js
$userInfo = $('#userInfo');
$loginBox = $('#loginBox');
$registerBox = $('#registerBox');
// 注册切换到登录
$registerBox.find('.colMint').on('click', function(){
$registerBox.hide();
$loginBox.show();
});
// 登录切换到注册
$loginBox.find('.colMint').on('click', function(){
$loginBox.hide();
$registerBox.show();
});;
// 注册功能实现
$registerBox.find('button').on('click', function(){
$.ajax({
type: 'post',
url: '/api/user/register',
data: {
username: $registerBox.find('[name="username"]').val(),
password: $registerBox.find('[name="password"]').val(),
repassword: $registerBox.find('[name="repassword"]').val()
},
dataType: 'json',
success: function(result){
console.log(result);
}
});
});
// api.js
/*
用户注册
前端验证
1、用户名不能为空
2、密码不能为空
3、两次密码必须一致
数据库验证
1、用户是否已经存在
1、用户不存在 - 保存用户注册信息到数据库中
*/
router.post('/user/register', function(req, res, next){
// 接收前端数据
var username = req.body.username;
var password = req.body.password;
var repassword = req.body.repassword;
// 用户名不能为空
if( username == '' ){
resData.code = 101;
resData.msg = '用户名不能为空';
res.json(resData);
return;
}
// 密码不能为空
if( password == '' ){
resData.code = 102;
resData.msg = '密码不能为空';
res.json(resData);
return;
}
// 两次密码必须一致
if( password != repassword ){
resData.code = 103;
resData.msg = '两次密码必须一致';
res.json(resData);
return;
}
// 用户是否已经存在
User.findOne({
username: username
}).then(function(userInfo){
if( userInfo ){
resData.code = 104;
resData.msg = '用户已被注册';
res.json(resData);
return;
}
// 保存用户注册信息到数据库中
return new User({
username: username,
password: password
}).save();
}).then(function(newUserInfo){
console.log(newUserInfo);
resData.msg = '注册成功';
res.json(resData);
});
});
用户登录
-
前端
- 登录页面渲染 - 用户注册、登录面板切换
- 登录功能实现 - ajax提交到api.js - /api/user/login
- 登录成功 - 显示用户信息标签
-
后端
- 登录功能(错误码:2xx)
-
前端验证
- 1、用户名不能为空
- 2、密码不能为空
-
数据库验证
- 1、用户名和密码是否正确
- 2、验证是否是管理员 - 在app.js页面中验证
-
登录成功
- 返回登录成功信息到前端
- 保存登录cookie信息
-
- 登录功能(错误码:2xx)
用户退出
- 前端
- 点击“退出” - 返送ajax - /api/user/logout
- 退出成功 - 重新刷新页面
- 后端
- 删除cookie
后台管理
首页
- 前端
- 使用bootstrap搭建后台管理首页
- 后端
- 验证是否是管理员用户
用户管理
-
数据库
-
前端
- 使用bootstrap搭建用户列表
-
后端
- 用户 - 列表
- 路由 - /user
- 渲染 - 用户列表
- 从数据库中获取用户数据
- 分页
- 用户 - 列表
分类管理
-
数据库
-
前端
- 使用bootstrap搭建分类列表
-
后端
- 分类 - 列表
- 路由 - /category
- 渲染 - 分类列表
- 从数据库获取分类数据
- 分页
- 分类 - 添加
- 路由 - /category/add
- 渲染 - 分类添加
- 处理 - 分类添加
- 接收前端数据
- 前端验证
- 1、分类名是否为空
- 后端验证
- 1、分类名是否存在
- 存在: 跳转错误提示【问题:Promise.reject(),卡在这里了】
- 不存在:添加分类名到数据库中
- 1、分类名是否存在
- 分类 - 修改
- 路由 - /category/edit
- 渲染 - 分类修改页面
- 1、 获取分类ID
- 2、查询 categories 数据库,显示该ID对应的内容
- 3、 render页面
- 处理 - 分类修改
- 前端验证
- 1、 分类ID不能为空
- 2、 分类名不能为空
- 数据库验证
- 1、 新分类名不能存在
- 更新
updateOne
新分类名到数据库中
- 前端验证
- 分类 - 删除
- 路由 - /category/delete
- 处理 - 分类删除
- 获取分类ID
- 操作数据库,执行删除语句-
remove()
- 分类 - 列表
文章管理
-
数据库
-
文章 - 列表
- 路由 - /article
- 渲染 - 文章列表页面
- 查询 articles 数据库
-
文章 - 添加
- 路由 - /article/add
- 渲染 - 文章添加页面
- 1、 查询categoires数据库,渲染分类
- 2、 渲染页面
- 处理 - 添加文章
- 1、 获取前端传递的数据
- 2、前端验证
- cateId是否为空
- title是否为空
- 3、 数据库验证
- cateId是否存在
- 4、 保存文章到数据库中
-
文章 - 修改
-
路由 - /article/edit
-
渲染 - 文章修改页面
- 1、获取文章id
- 2、数据库验证
- 文章id是否存在
- 根据id,查询 article数据表
- 3、查询所有的分类数据
- 4、渲染查询到的文章信息(附带分类数据、文章数据)
-
处理 - 添加文章
-
1、获取前端传递的文章数据
-
2、前端验证
-
cateId是否为空
-
title是否为空
-
-
3、 数据库验证【问题:此处在接收前端post数据的同时也get了url中的id参数数据,导致出现
CastError: Cast to ObjectId failed
错误】-
article的id是否存在
-
cateId是否存在
-
-
4、 更新文章
-
-
-
文章 - 删除
- 路由 - /article/delete
- 处理 - 文章删除
- 1、获取文章id
- 2、数据库验证
- 文章id是否存在
- 3、根据id删除文章
前台
首页
- 首页 - 渲染
- 1、渲染导航分类标签
- 获取分类ID - category
- 获取数据库中的分类数据
- 2、渲染文章列表
- 获取数据库中总行数
- 计算分页所需要的参数
- 根据以上参数获取文章数据(分页、关联categories和users表)
- 统一渲染index.html
- 3、根据导航分类显示不同分类的文章
- category是否为空
- 为空:where为空对象
- 不为空:where条件根据cateId分类文章
- category是否为空
- 1、渲染导航分类标签
详情页
- 详情页 - 渲染
- 1、获取URL参数:articleId
- 验证是否存在articleId
- 2、根据articleId查询数据库,获取该文章的所有信息
- 3、阅读数的自增
- 4、渲染该文章
- 1、获取URL参数:articleId
评论
- 数据库
- 前端
- 初始化就显示评论
- 点击提交按钮,提交评论
- 点击上一页、下一页,实现评论翻页
- 渲染评论,带分页
- 格式化时间
- 后端
- 评论 - 显示
- 1、获取文章id
- 2、查询articles数据表
- 3、通过json返回前端
- 评论 - 添加
- 1、获取文章id
- 2、生成一条评论对象
- 3、查询articles数据表
- 4、将生成的评论push到返回的数据中
- 5、保存新的article数据到表中,并返回新的article数据
- 6、通过json返回新的article数据到前端
- 评论 - 显示