摘要:前端渲染可以減輕服務器端的開銷,但是首屏的渲染會加長時間后端渲染增加服務器的開銷,但是減少客戶端展示的時間
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的方式將數據添加到頁面。
前端渲染:可以減輕服務器端的開銷,但是首屏的渲染會加長時間
后端渲染:增加服務器的開銷,但是減少客戶端展示的時間
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/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...
閱讀 3988·2021-09-22 15:28
閱讀 1518·2021-09-03 10:35
閱讀 1097·2021-09-02 15:21
閱讀 3646·2019-08-30 15:53
閱讀 3649·2019-08-29 17:25
閱讀 705·2019-08-29 13:22
閱讀 1722·2019-08-28 18:15
閱讀 2587·2019-08-26 13:57