摘要:前端渲染可以減輕服務(wù)器端的開銷,但是首屏的渲染會(huì)加長時(shí)間后端渲染增加服務(wù)器的開銷,但是減少客戶端展示的時(shí)間
1 注冊、登錄和退出 1.1 用戶注冊、登錄
配置模板引擎、mongoDB數(shù)據(jù)庫驅(qū)動(dòng)、靜態(tài)文件路徑和post請求解析中間件
統(tǒng)一api.js路由的數(shù)據(jù)返回格式
// 統(tǒng)一返回?cái)?shù)據(jù)格式 var responseData; // 每次請求進(jìn)來都進(jìn)行初始化 router.use(function (res, req, next) { responseData = { code: 0, // 狀態(tài)碼,默認(rèn)為0 message: "" // 狀態(tài)碼的提示信息,默認(rèn)為0 }; next(); // 調(diào)用next()交由下一個(gè)中間件繼續(xù)處理 });
設(shè)計(jì)用戶的數(shù)據(jù)模型設(shè)計(jì)與創(chuàng)建
var mongoose = require("mongoose"); var Schema = mongoose.Schema; // userSchema代表名為用戶的collection集合 var usersSchema = new Schema({ // 每個(gè)屬性代表collection中的每個(gè)document // 用戶名: 字符串 username: String, // 密碼: 字符串 password: String, // 是否為管理員,默認(rèn)為false isAdmin: { type: Boolean, default: false } }); // 對外導(dǎo)出定義的用戶的collection結(jié)構(gòu) module.exports = usersSchema;
var mongoose = require("mongoose"); // 加載創(chuàng)建的usersSchema模型 var usersSchema = require("../schemas/users"); // 利用usersSchema創(chuàng)建Model,使用mongoose.model()方法 // 第一個(gè)參數(shù)是模型的名字;第二個(gè)參數(shù)是創(chuàng)建模型的數(shù)據(jù)結(jié)構(gòu) // User是一個(gè)構(gòu)造函數(shù),可以利用Model直接操作collection,也可以實(shí)例化對象來操作每個(gè)document var User = mongoose.model("User", usersSchema); // 對外暴露模型,提供給業(yè)務(wù)邏輯操作 module.exports = User;
完成注冊邏輯
前端將數(shù)據(jù)提交到指定路由(Ajax或整頁刷新)
服務(wù)器獲取提交的數(shù)據(jù),進(jìn)行基本驗(yàn)證與數(shù)據(jù)庫查重驗(yàn)證
如果數(shù)據(jù)庫中用戶名已經(jīng)存在,返回錯(cuò)誤信息;如果不存在,則保存當(dāng)前注冊信息
完成登錄邏輯
前端利將數(shù)據(jù)提交到指定路由(Ajax或整頁刷新)
服務(wù)器通過body-parser中間件獲取post請求中的數(shù)據(jù)req.body;通過req.query獲取get請求中的數(shù)據(jù),進(jìn)行基本驗(yàn)證
進(jìn)行數(shù)據(jù)庫查詢驗(yàn)證:使用username和password兩個(gè)字段進(jìn)行查詢,如果存在,則返回登錄成功;否則登錄失敗
同時(shí),為登錄成功的用戶發(fā)送一個(gè)cookie,保存必要的信息,但不能是密碼等敏感信息,用于保存用戶的登錄狀態(tài),cookie只有在瀏覽器沒有上傳cookie是才發(fā)送,并且只發(fā)送一次,注意設(shè)置過期時(shí)間
// 設(shè)置Cookie,每個(gè)請求進(jìn)入路由處理前,先處理req對象中的cookie信息 // 用戶無論何時(shí)訪問站點(diǎn),都通通過這個(gè)中間件,并且通過next()方法將返回值傳遞下去 // 在登錄成功后,通過cookies.set()方法將cookie一起返回給瀏覽器 // 第一次登錄時(shí),客戶端沒有cookie,需要發(fā)送一個(gè)cookie回去; app.use(function (req, res, next) { req.cookies = new Cookies(req, res); // 在訪問admin中評論、留言等功能時(shí)都需要用到登錄信息,定義一個(gè)全局的req對象的屬性,來保存用戶登錄的cookie信息 req.userInfo = {}; if(req.cookies.get("userInfo")) { try { req.userInfo = JSON.parse(req.cookies.get("userInfo")); // 將cookie解析為一個(gè)對象 // 需要實(shí)時(shí)獲取當(dāng)前的用戶是否為管理員,查詢數(shù)據(jù)庫,利用當(dāng)前用戶的_id查詢 User.findById({_id: req.userInfo._id}).then(function (userInfo) { req.userInfo.isAdmin = Boolean(userInfo.isAdmin); // 新增req對象的一個(gè)全局屬性,判斷是否非管理員 next(); }) } catch (e) { next(); } } else { next(); } });
// 發(fā)送一個(gè)cookie,瀏覽器會(huì)緩存cookie,以后每次請求,瀏覽器都會(huì)帶上這個(gè)cookie // cookie應(yīng)該能唯一標(biāo)識(shí)一個(gè)用戶,所以使用用戶信息作為cookie,cookie是一個(gè)字符串 req.cookies.set("userInfo", JSON.stringify({ _id: userInfo._id, username: userInfo.username }));
刷新頁面時(shí),瀏覽器將cookie中的數(shù)據(jù)發(fā)送到服務(wù)器,服務(wù)器利用cookie中的信息完成登錄頁面的展示
利用cookie判斷是否 為管理員(是否是管理員的信息一般不放在cookie中),登錄后臺(tái)管理界面
1.2 退出利用cookies模塊,將cookie字段設(shè)置為null,然后在客戶端刷新頁面,即未登錄狀態(tài)下展示的頁面
router.get("/", function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });2 后臺(tái)管理 2.1 判斷是否為管理員
利用cookie中添加的后續(xù)信息,判斷是否為管理員,只有管理員才能繼續(xù)后續(xù)操作
// 利用中間件判斷是否為管理員賬戶 router.use(function (req, res, next) { if(!req.userInfo.isAdmin) { res.send("Sorry, it"s only for Administor!"); return; } // 如果是管理員,繼續(xù)下面的操作 next(); });2.2 后臺(tái)信息展示界面
利用get請求,獲取后臺(tái)頁面,傳入cookie中的信息req.userInfo
router.get("/", function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });2.3 用戶信息的展示
同樣利用get請求,從數(shù)據(jù)庫中查詢所有的用戶信息,并將數(shù)據(jù)返回前端。
分頁展示數(shù)據(jù)的功能通過:limit()和skip()約束實(shí)現(xiàn)
倒序通過sort({_id: -1})約束實(shí)現(xiàn)
同時(shí)查詢關(guān)聯(lián)字段的數(shù)據(jù)通過.populate(["category", "article"])實(shí)現(xiàn)
router.get("/user", function (req, res, next) { /* 從數(shù)據(jù)庫中讀取數(shù)據(jù),每頁展現(xiàn)的數(shù)據(jù)數(shù)量相同,內(nèi)容不相同 * 1、limit()約束限制取出數(shù)據(jù)的條數(shù) * 2、skip()約束限制開始取數(shù)據(jù)的位置,skip(2)表示忽略前兩天數(shù)據(jù),從第三條開始取 * 3、每頁顯示4條 * 第1頁: skip(0) --> 當(dāng)前頁 - 1 * limit * 第2頁: skip(4) * 第3頁: skip(8) */ var page = req.query.page || 1; // 如果用戶不傳,默認(rèn)為第1頁 var limit = 10; var pages = 0; // 保存總的頁數(shù) User.count().then(function (count) { pages = Math.ceil(count / limit); // 當(dāng)前頁數(shù)不能大于總頁數(shù)pages page = Math.min(page, pages); // 當(dāng)前頁數(shù)不能小于1 page = Math.max(1, page); var skip = (page - 1) * limit; // 計(jì)算page之后,確定需要skip的個(gè)數(shù) User.find().limit(limit).skip(skip).then(function (users) { res.render("admin/user_index", { userInfo: req.userInfo, users: users, count: count, // 總的數(shù)據(jù)條數(shù) pages: pages, // 總頁數(shù) limit: limit, // 每頁顯示幾條數(shù)據(jù) page: page // 當(dāng)前頁數(shù) }); }) }) });2.4 分類信息的添加
構(gòu)建分類信息的數(shù)據(jù)結(jié)構(gòu)和模型
var mongoose = require("mongoose"); var Schema = mongoose.Schema; // categoriesSchema代表名為用戶的collection集合 var categoriesSchema = new Schema({ // 每個(gè)屬性代表collection中的每個(gè)document // 分類名稱 name: String }); // 對外導(dǎo)出定義的用戶的collection結(jié)構(gòu) module.exports = categoriesSchema;
var mongoose = require("mongoose"); var categoriesSchema = require("../schemas/categories"); var Category = mongoose.model("Category", categoriesSchema); module.exports = Category;
完成get路由獲取展示分類的頁面(包括修改和刪除分類的入口):通過數(shù)據(jù)庫查詢獲取所有的分類信息,同樣利用limit()、skip()實(shí)現(xiàn)分頁
Category.find().sort({_id: -1}).limit(limit).skip(skip).then(function (categories){...}
完成get路由獲取分類添加的頁面,利用post請求提交數(shù)據(jù)
后端通過post路由獲取添加分類的數(shù)據(jù),進(jìn)行基本驗(yàn)證與數(shù)據(jù)庫查重:如果通過,則保存數(shù)據(jù);否則返回錯(cuò)誤信息。利用findOne()方法查詢一條記錄;
new Model().save()方法保存數(shù)據(jù)
// 分類添加的數(shù)據(jù)提交后保存 router.post("/category/add", function (req, res, next) { // 如果用戶沒有輸入數(shù)據(jù),或提交的數(shù)據(jù)不符合需求的格式 // 沒有使用Ajax,所以不符合時(shí)直接跳轉(zhuǎn)到另外一個(gè)錯(cuò)誤頁面 var category = req.body.category || ""; if(!req.body.category) { // 如果名稱為空,跳轉(zhuǎn)到錯(cuò)誤頁面 res.render("admin/error", { userInfo: req.userInfo, message: "分類名稱不能為空" }); return; } // 數(shù)據(jù)庫中是否已經(jīng)存在相同的分類名稱 Category.findOne({name: category}).then(function (rs) { // 數(shù)據(jù)庫中已經(jīng)存在該分類 if(rs) { res.render("admin/error", { userInfo: req.userInfo, message: "分類已經(jīng)存在" }); return Promise.reject(); // 退出異步執(zhí)行 } else { // 數(shù)據(jù)庫中不存在該分類,創(chuàng)建Category的實(shí)例對象保存到數(shù)據(jù)庫 return new Category({ name: category }).save(); } }).then(function (newCategory) { res.render("admin/success", { // 渲染分類成功的頁面 userInfo: req.userInfo, message: "分類保存成功", url: "/admin/category" }); }); });2.5 分類信息的修改與刪除
通過分類信息展示頁的入口,通過get請求完成分類修改頁面還原;再利用post請求將修改的數(shù)據(jù)進(jìn)行更新
前端通過url傳入修改和刪除分類的_id
通過數(shù)據(jù)庫查詢到該條記錄,返回原有數(shù)據(jù),渲染到編輯分類頁面,展示原來的分類名稱
分類修改后,利用post請求上傳數(shù)據(jù):_id和修改內(nèi)容
后臺(tái)拿到數(shù)據(jù)后,先進(jìn)行基本驗(yàn)證;數(shù)據(jù)庫驗(yàn)證(查詢分類名是否已經(jīng)存在)
Category.findOne({ id: {$ne: id}, // 不同的記錄中是否存在相同的分類名稱 name: nameCategory })
如果分類名不存在,再利用update()更新本條數(shù)據(jù)
// 分類信息修改保存 router.post("/category/edit", function (req, res, next) { // 獲取要修改的分類信息 var id = req.query.id; // 獲取post請求提交的分類名稱數(shù)據(jù) var nameCategory = req.body.category; // 查看提交的分類名稱數(shù)據(jù)是否存在 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) { // 如果沒有修改,直接提示修改成功,跳轉(zhuǎn)到首頁 res.render("admin/success", { userInfo: req.userInfo, message: "修改成功", url: "/admin/category" }); return Promise.reject(); } else { // 判斷添加的分類名稱是否已經(jīng)存在 Category.findOne({ id: {$ne: id}, // 不同的記錄中是否存在相同的分類名稱 name: nameCategory }).then(function (sameCategory) { if(sameCategory) { res.render("admin/error", { userInfo: req.userInfo, message: "分類名稱已經(jīng)存在" }); return Promise.reject(); } else { // 如果不重復(fù),則保存改的數(shù)據(jù) Category.update({_id: id}, {name: nameCategory}).then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "修改成功", url: "/admin/category" }); }) } }) } } }) });
數(shù)據(jù)庫的刪除只需使用remove({_id: id})即可
router.get("/category/delete", function (req, res, next) { // 獲取要?jiǎng)h除分類的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 文章管理
文章管理的實(shí)現(xiàn)邏輯與分類管理基本一致:
完成文章管理的列表展示頁,有添加文章的入口
// 文章首頁 router.get("/article", function (req, res, next) { // 從數(shù)據(jù)庫獲取文章的內(nèi)容 var page = req.query.page || 1; var limit = 10; var pages = 0; // 分類管理時(shí),常識(shí)應(yīng)該講新添加的分類放在最前面,所以展示時(shí)應(yīng)該降序從數(shù)據(jù)庫中讀取數(shù)據(jù) 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()約束有兩個(gè)值: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提交填寫的數(shù)據(jù)
router.get("/article/add", function (req, res, next) { // 從服務(wù)器中讀取所有分類信息 Category.find().sort({_id:-1}).then(function (categories) { res.render("admin/article_add", { userInfo: req.userInfo, categories: categories }); }); });
后端解析獲取文章的數(shù)據(jù),進(jìn)行基本驗(yàn)證,通過后進(jìn)行數(shù)據(jù)保存
// 文章內(nèi)容保存路由 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); // 基本驗(yàn)證,分類、標(biāo)題、簡介、內(nèi)容不能為空 if(!categoryId) { res.render("admin/error", { userInfo: req.userInfo, message: "文章分類不能為空" }); return; } if(!title) { res.render("admin/error", { userInfo: req.userInfo, message: "文章標(biāo)題不能為空" }); return; } if(!description) { res.render("admin/error", { userInfo: req.userInfo, message: "文章簡介不能為空" }); return; } if(!article) { res.render("admin/error", { userInfo: req.userInfo, message: "文章內(nèi)容不能為空" }); return; } // 保存數(shù)據(jù)到數(shù)據(jù)庫 return new Article({ // 利用Model創(chuàng)建一個(gè)實(shí)例對象,利用save()方法保存數(shù)據(jù) 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" }); }) });
文章的修改有兩個(gè)步驟:首先是獲取文章的編輯頁,將原來的內(nèi)容渲染到頁面上,編輯修改后,將數(shù)據(jù)提交到后臺(tái);后臺(tái)完成基本驗(yàn)證后,更新數(shù)據(jù)庫中的內(nèi)容
// 文章內(nèi)容修改的數(shù)據(jù)提交 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: "文章標(biāo)題不能為空" }); return; } if(!description) { res.render("admin/error", { userInfo: req.userInfo, message: "文章簡介不能為空" }); return; } if(!article) { res.render("admin/error", { userInfo: req.userInfo, message: "文章內(nèi)容不能為空" }); return; } Article.update({_id: id}, { category: category, description: description, title: title, article: article }).then(function () { res.render("admin/success", { userInfo: req.userInfo, message: "內(nèi)容修改成功", 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 評論的管理
評論可以多帶帶存放在一個(gè)coolection中,便于各種操作管理,通用性強(qiáng),可以編輯和刪除;增加復(fù)雜度
將評論存儲(chǔ)為文章的一個(gè)字段,與一片文章綁定在一起,操作簡便,但是編輯與刪除功能很難實(shí)現(xiàn)
3 前臺(tái)展示通過后端將數(shù)據(jù)返回之后,服務(wù)器端一般將數(shù)據(jù)構(gòu)造為JSON格式,便于操作,可以利用后端模板或者前端操作DOM的方式將數(shù)據(jù)添加到頁面。
前端渲染:可以減輕服務(wù)器端的開銷,但是首屏的渲染會(huì)加長時(shí)間
后端渲染:增加服務(wù)器的開銷,但是減少客戶端展示的時(shí)間
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86955.html
摘要:本項(xiàng)目持續(xù)更新中,開源免費(fèi)與各位愛好技術(shù)達(dá)人共勉,注現(xiàn)階段仍在開發(fā)中。。。。。 NodeJS+Express+MongoDb開發(fā)的個(gè)人博客 NodeJS+Express搭建個(gè)人博客-環(huán)境搭建(一)NodeJS+Express搭建個(gè)人博客-gulp自動(dòng)化構(gòu)建工具使用(二)NodeJS+Express搭建個(gè)人博客-Express+Mongodb組合架構(gòu)介紹(三)NodeJS+Express...
摘要:前言學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講項(xiàng)目地址效果后臺(tái)管理系統(tǒng)前端頁面架構(gòu)可以看到,在整個(gè)項(xiàng)目中,沒有頁面的跳轉(zhuǎn)只有前后端的數(shù)據(jù)交換,所有的頁面更新都是組件更新和數(shù)據(jù)更新后端只對數(shù) 前言 學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講 項(xiàng)目github地址:https://git...
摘要:前言學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講項(xiàng)目地址效果后臺(tái)管理系統(tǒng)前端頁面架構(gòu)可以看到,在整個(gè)項(xiàng)目中,沒有頁面的跳轉(zhuǎn)只有前后端的數(shù)據(jù)交換,所有的頁面更新都是組件更新和數(shù)據(jù)更新后端只對數(shù) 前言 學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講 項(xiàng)目github地址:https://git...
摘要:前言學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講項(xiàng)目地址效果后臺(tái)管理系統(tǒng)前端頁面架構(gòu)可以看到,在整個(gè)項(xiàng)目中,沒有頁面的跳轉(zhuǎn)只有前后端的數(shù)據(jù)交換,所有的頁面更新都是組件更新和數(shù)據(jù)更新后端只對數(shù) 前言 學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講 項(xiàng)目github地址:https://git...
摘要:前言學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講項(xiàng)目地址效果后臺(tái)管理系統(tǒng)前端頁面架構(gòu)可以看到,在整個(gè)項(xiàng)目中,沒有頁面的跳轉(zhuǎn)只有前后端的數(shù)據(jù)交換,所有的頁面更新都是組件更新和數(shù)據(jù)更新后端只對數(shù) 前言 學(xué)習(xí)前端也有一段時(shí)間了做個(gè)個(gè)人博客網(wǎng)站吧正好總結(jié)練習(xí)一下這段時(shí)間的所學(xué)文章很長,會(huì)拆成三篇來講 項(xiàng)目github地址:https://git...
閱讀 3672·2021-09-22 15:28
閱讀 1296·2021-09-03 10:35
閱讀 878·2021-09-02 15:21
閱讀 3474·2019-08-30 15:53
閱讀 3496·2019-08-29 17:25
閱讀 569·2019-08-29 13:22
閱讀 1555·2019-08-28 18:15
閱讀 2286·2019-08-26 13:57