摘要:前端渲染可以减轻服务器端的开销,但是首屏的渲染会加长时间后端渲染增加服务器的开销,但是减少客户端展示的时间
1 注册、登录和退出 1.1 用户注册、登录
配置模板引擎、mongoDB数据库驱动、静态文件路径和post请求解析中间件
统一api.js路由的数据返回格式
// 统一返回数据格式 var responseData; // 每次请求进来都进行初始化 router.use(function (res, req, next) { responseData = { code: 0, // 状态码,默认为0 message: "" // 状态码的提示信息,默认为0 }; next(); // 调用next()交由下一个中间件继续处理 });
设计用户的数据模型设计与创建
var mongoose = require("mongoose"); var Schema = mongoose.Schema; // userSchema代表名为用户的collection集合 var usersSchema = new Schema({ // 每个属性代表collection中的每个document // 用户名: 字符串 username: String, // 密码: 字符串 password: String, // 是否为管理员,默认为false isAdmin: { type: Boolean, default: false } }); // 对外导出定义的用户的collection结构 module.exports = usersSchema;
var mongoose = require("mongoose"); // 加载创建的usersSchema模型 var usersSchema = require("../schemas/users"); // 利用usersSchema创建Model,使用mongoose.model()方法 // 第一个参数是模型的名字;第二个参数是创建模型的数据结构 // User是一个构造函数,可以利用Model直接操作collection,也可以实例化对象来操作每个document var User = mongoose.model("User", usersSchema); // 对外暴露模型,提供给业务逻辑操作 module.exports = User;
完成注册逻辑
前端将数据提交到指定路由(Ajax或整页刷新)
服务器获取提交的数据,进行基本验证与数据库查重验证
如果数据库中用户名已经存在,返回错误信息;如果不存在,则保存当前注册信息
完成登录逻辑
前端利将数据提交到指定路由(Ajax或整页刷新)
服务器通过body-parser中间件获取post请求中的数据req.body;通过req.query获取get请求中的数据,进行基本验证
进行数据库查询验证:使用username和password两个字段进行查询,如果存在,则返回登录成功;否则登录失败
同时,为登录成功的用户发送一个cookie,保存必要的信息,但不能是密码等敏感信息,用于保存用户的登录状态,cookie只有在浏览器没有上传cookie是才发送,并且只发送一次,注意设置过期时间
// 设置Cookie,每个请求进入路由处理前,先处理req对象中的cookie信息 // 用户无论何时访问站点,都通通过这个中间件,并且通过next()方法将返回值传递下去 // 在登录成功后,通过cookies.set()方法将cookie一起返回给浏览器 // 第一次登录时,客户端没有cookie,需要发送一个cookie回去; app.use(function (req, res, next) { req.cookies = new Cookies(req, res); // 在访问admin中评论、留言等功能时都需要用到登录信息,定义一个全局的req对象的属性,来保存用户登录的cookie信息 req.userInfo = {}; if(req.cookies.get("userInfo")) { try { req.userInfo = JSON.parse(req.cookies.get("userInfo")); // 将cookie解析为一个对象 // 需要实时获取当前的用户是否为管理员,查询数据库,利用当前用户的_id查询 User.findById({_id: req.userInfo._id}).then(function (userInfo) { req.userInfo.isAdmin = Boolean(userInfo.isAdmin); // 新增req对象的一个全局属性,判断是否非管理员 next(); }) } catch (e) { next(); } } else { next(); } });
// 发送一个cookie,浏览器会缓存cookie,以后每次请求,浏览器都会带上这个cookie // cookie应该能唯一标识一个用户,所以使用用户信息作为cookie,cookie是一个字符串 req.cookies.set("userInfo", JSON.stringify({ _id: userInfo._id, username: userInfo.username }));
刷新页面时,浏览器将cookie中的数据发送到服务器,服务器利用cookie中的信息完成登录页面的展示
利用cookie判断是否 为管理员(是否是管理员的信息一般不放在cookie中),登录后台管理界面
1.2 退出利用cookies模块,将cookie字段设置为null,然后在客户端刷新页面,即未登录状态下展示的页面
router.get("/", function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });2 后台管理 2.1 判断是否为管理员
利用cookie中添加的后续信息,判断是否为管理员,只有管理员才能继续后续操作
// 利用中间件判断是否为管理员账户 router.use(function (req, res, next) { if(!req.userInfo.isAdmin) { res.send("Sorry, it"s only for Administor!"); return; } // 如果是管理员,继续下面的操作 next(); });2.2 后台信息展示界面
利用get请求,获取后台页面,传入cookie中的信息req.userInfo
router.get("/", function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });2.3 用户信息的展示
同样利用get请求,从数据库中查询所有的用户信息,并将数据返回前端。
分页展示数据的功能通过:limit()和skip()约束实现
倒序通过sort({_id: -1})约束实现
同时查询关联字段的数据通过.populate(["category", "article"])实现
router.get("/user", function (req, res, next) { /* 从数据库中读取数据,每页展现的数据数量相同,内容不相同 * 1、limit()约束限制取出数据的条数 * 2、skip()约束限制开始取数据的位置,skip(2)表示忽略前两天数据,从第三条开始取 * 3、每页显示4条 * 第1页: skip(0) --> 当前页 - 1 * limit * 第2页: skip(4) * 第3页: skip(8) */ var page = req.query.page || 1; // 如果用户不传,默认为第1页 var limit = 10; var pages = 0; // 保存总的页数 User.count().then(function (count) { pages = Math.ceil(count / limit); // 当前页数不能大于总页数pages page = Math.min(page, pages); // 当前页数不能小于1 page = Math.max(1, page); var skip = (page - 1) * limit; // 计算page之后,确定需要skip的个数 User.find().limit(limit).skip(skip).then(function (users) { res.render("admin/user_index", { userInfo: req.userInfo, users: users, count: count, // 总的数据条数 pages: pages, // 总页数 limit: limit, // 每页显示几条数据 page: page // 当前页数 }); }) }) });2.4 分类信息的添加
构建分类信息的数据结构和模型
var mongoose = require("mongoose"); var Schema = mongoose.Schema; // categoriesSchema代表名为用户的collection集合 var categoriesSchema = new Schema({ // 每个属性代表collection中的每个document // 分类名称 name: String }); // 对外导出定义的用户的collection结构 module.exports = categoriesSchema;
var mongoose = require("mongoose"); var categoriesSchema = require("../schemas/categories"); var Category = mongoose.model("Category", categoriesSchema); module.exports = Category;
完成get路由获取展示分类的页面(包括修改和删除分类的入口):通过数据库查询获取所有的分类信息,同样利用limit()、skip()实现分页
Category.find().sort({_id: -1}).limit(limit).skip(skip).then(function (categories){...}
完成get路由获取分类添加的页面,利用post请求提交数据
后端通过post路由获取添加分类的数据,进行基本验证与数据库查重:如果通过,则保存数据;否则返回错误信息。利用findOne()方法查询一条记录;
new Model().save()方法保存数据
// 分类添加的数据提交后保存 router.post("/category/add", function (req, res, next) { // 如果用户没有输入数据,或提交的数据不符合需求的格式 // 没有使用Ajax,所以不符合时直接跳转到另外一个错误页面 var category = req.body.category || ""; if(!req.body.category) { // 如果名称为空,跳转到错误页面 res.render("admin/error", { userInfo: req.userInfo, message: "分类名称不能为空" }); return; } // 数据库中是否已经存在相同的分类名称 Category.findOne({name: category}).then(function (rs) { // 数据库中已经存在该分类 if(rs) { res.render("admin/error", { userInfo: req.userInfo, message: "分类已经存在" }); return Promise.reject(); // 退出异步执行 } else { // 数据库中不存在该分类,创建Category的实例对象保存到数据库 return new Category({ name: category }).save(); } }).then(function (newCategory) { res.render("admin/success", { // 渲染分类成功的页面 userInfo: req.userInfo, message: "分类保存成功", url: "/admin/category" }); }); });2.5 分类信息的修改与删除
通过分类信息展示页的入口,通过get请求完成分类修改页面还原;再利用post请求将修改的数据进行更新
前端通过url传入修改和删除分类的_id
通过数据库查询到该条记录,返回原有数据,渲染到编辑分类页面,展示原来的分类名称
分类修改后,利用post请求上传数据:_id和修改内容
后台拿到数据后,先进行基本验证;数据库验证(查询分类名是否已经存在)
Category.findOne({ id: {$ne: id}, // 不同的记录中是否存在相同的分类名称 name: nameCategory })
如果分类名不存在,再利用update()更新本条数据
// 分类信息修改保存 router.post("/category/edit", function (req, res, next) { // 获取要修改的分类信息 var id = req.query.id; // 获取post请求提交的分类名称数据 var nameCategory = req.body.category; // 查看提交的分类名称数据是否存在 Category.findOne({ _id: id }).then(function (category) { if(!category) { res.render("admin/error", { userInfo: req.userInfo, message: "分类信息不存在" }); return Promise.reject(); } else { // 判断用户是否做了修改 if(nameCategory === category.name) { // 如果没有修改,直接提示修改成功,跳转到首页 res.render("admin/success", { userInfo: req.userInfo, message: "修改成功", url: "/admin/category" }); return Promise.reject(); } else { // 判断添加的分类名称是否已经存在 Category.findOne({ id: {$ne: id}, // 不同的记录中是否存在相同的分类名称 name: nameCategory }).then(function (sameCategory) { if(sameCategory) { res.render("admin/error", { userInfo: req.userInfo, message: "分类名称已经存在" }); return Promise.reject(); } else { // 如果不重复,则保存改的数据 Category.update({_id: id}, {name: nameCategory}).then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "修改成功", url: "/admin/category" }); }) } }) } } }) });
数据库的删除只需使用remove({_id: id})即可
router.get("/category/delete", function (req, res, next) { // 获取要删除分类的id var id = req.query.id; Category.remove({_id: id}).then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "删除成功", url: "/admin/category" }); }) });2.6 文章管理
文章管理的实现逻辑与分类管理基本一致:
完成文章管理的列表展示页,有添加文章的入口
// 文章首页 router.get("/article", function (req, res, next) { // 从数据库获取文章的内容 var page = req.query.page || 1; var limit = 10; var pages = 0; // 分类管理时,常识应该讲新添加的分类放在最前面,所以展示时应该降序从数据库中读取数据 Article.count().then(function (count) { pages = Math.ceil(count / limit); page = Math.min(page, pages); page = Math.max(1, page); var skip = (page - 1) * limit; // sort()约束有两个值:1表示升序;-1表示降序 Article.find().sort({_id: -1}).limit(limit).skip(skip).populate(["category", "user"]).then(function (articles) { console.log(articles); res.render("admin/article_index", { userInfo: req.userInfo, articles: articles, pages: pages, count: count, limit: limit, page: page }); }); }); });
完成文章添加页的表单,通过post提交填写的数据
router.get("/article/add", function (req, res, next) { // 从服务器中读取所有分类信息 Category.find().sort({_id:-1}).then(function (categories) { res.render("admin/article_add", { userInfo: req.userInfo, categories: categories }); }); });
后端解析获取文章的数据,进行基本验证,通过后进行数据保存
// 文章内容保存路由 router.post("/article/add", function (req, res, next) { var categoryId = req.body.category; var description = req.body.description; var title = req.body.title; var article = req.body.article; // console.log(categoryId, description, title, article); // 基本验证,分类、标题、简介、内容不能为空 if(!categoryId) { res.render("admin/error", { userInfo: req.userInfo, message: "文章分类不能为空" }); return; } if(!title) { res.render("admin/error", { userInfo: req.userInfo, message: "文章标题不能为空" }); return; } if(!description) { res.render("admin/error", { userInfo: req.userInfo, message: "文章简介不能为空" }); return; } if(!article) { res.render("admin/error", { userInfo: req.userInfo, message: "文章内容不能为空" }); return; } // 保存数据到数据库 return new Article({ // 利用Model创建一个实例对象,利用save()方法保存数据 category: categoryId, user: req.userInfo._id.toString(), title: title, description: description, article: article }).save().then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "文章添加成功", url: "/admin/article" }); }) });
文章的修改有两个步骤:首先是获取文章的编辑页,将原来的内容渲染到页面上,编辑修改后,将数据提交到后台;后台完成基本验证后,更新数据库中的内容
// 文章内容修改的数据提交 router.post("/article/edit", function (req, res, next) { // 获取文章的id var id = req.query.id; var category = req.body.category; var title = req.body.title; var description = req.body.description; var article = req.body.article; if(!category) { res.render("admin/error", { userInfo: req.userInfo, message: "文章分类不能为空" }); return; } if(!title) { res.render("admin/error", { userInfo: req.userInfo, message: "文章标题不能为空" }); return; } if(!description) { res.render("admin/error", { userInfo: req.userInfo, message: "文章简介不能为空" }); return; } if(!article) { res.render("admin/error", { userInfo: req.userInfo, message: "文章内容不能为空" }); return; } Article.update({_id: id}, { category: category, description: description, title: title, article: article }).then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "内容修改成功", url: "/admin/article" }) }); });
文章的删除与分类删除一致:使用remove()方法
router.get("/article/delete", function (req, res, next) { var id = req.query.id || ""; if(!id) { res.render("admin/error", { userInfo: req.userInfo, message: "指定 文章不存在" }) return; } Article.remove({_id: id}).then(function () { res.render("admin/error", { userInfo: req.userInfo, message: "删除成功", url: "/admin/article" }); }) });2.7 评论的管理
评论可以多带带存放在一个coolection中,便于各种操作管理,通用性强,可以编辑和删除;增加复杂度
将评论存储为文章的一个字段,与一片文章绑定在一起,操作简便,但是编辑与删除功能很难实现
3 前台展示通过后端将数据返回之后,服务器端一般将数据构造为JSON格式,便于操作,可以利用后端模板或者前端操作DOM的方式将数据添加到页面。
前端渲染:可以减轻服务器端的开销,但是首屏的渲染会加长时间
后端渲染:增加服务器的开销,但是减少客户端展示的时间
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/86955.html
摘要:本项目持续更新中,开源免费与各位爱好技术达人共勉,注现阶段仍在开发中。。。。。 NodeJS+Express+MongoDb开发的个人博客 NodeJS+Express搭建个人博客-环境搭建(一)NodeJS+Express搭建个人博客-gulp自动化构建工具使用(二)NodeJS+Express搭建个人博客-Express+Mongodb组合架构介绍(三)NodeJS+Express...
摘要:前言学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲项目地址效果后台管理系统前端页面架构可以看到,在整个项目中,没有页面的跳转只有前后端的数据交换,所有的页面更新都是组件更新和数据更新后端只对数 前言 学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲 项目github地址:https://git...
摘要:前言学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲项目地址效果后台管理系统前端页面架构可以看到,在整个项目中,没有页面的跳转只有前后端的数据交换,所有的页面更新都是组件更新和数据更新后端只对数 前言 学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲 项目github地址:https://git...
摘要:前言学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲项目地址效果后台管理系统前端页面架构可以看到,在整个项目中,没有页面的跳转只有前后端的数据交换,所有的页面更新都是组件更新和数据更新后端只对数 前言 学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲 项目github地址:https://git...
摘要:前言学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲项目地址效果后台管理系统前端页面架构可以看到,在整个项目中,没有页面的跳转只有前后端的数据交换,所有的页面更新都是组件更新和数据更新后端只对数 前言 学习前端也有一段时间了做个个人博客网站吧正好总结练习一下这段时间的所学文章很长,会拆成三篇来讲 项目github地址:https://git...
阅读 3671·2021-09-22 15:28
阅读 1295·2021-09-03 10:35
阅读 878·2021-09-02 15:21
阅读 3474·2019-08-30 15:53
阅读 3495·2019-08-29 17:25
阅读 569·2019-08-29 13:22
阅读 1555·2019-08-28 18:15
阅读 2285·2019-08-26 13:57