配置模板引擎、mongoDB
數據庫驅動、靜態文件路徑和post
請求解析中間件html
統一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或整頁刷新)api
服務器獲取提交的數據,進行基本驗證與數據庫查重驗證瀏覽器
若是數據庫中用戶名已經存在,返回錯誤信息;若是不存在,則保存當前註冊信息緩存
完成登陸邏輯服務器
前端利將數據提交到指定路由(Ajax或整頁刷新)cookie
服務器經過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
中),登陸後臺管理界面
利用cookies
模塊,將cookie
字段設置爲null
,而後在客戶端刷新頁面,即未登陸狀態下展現的頁面
router.get('/', function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });
利用cookie
中添加的後續信息,判斷是否爲管理員,只有管理員才能繼續後續操做
// 利用中間件判斷是否爲管理員帳戶 router.use(function (req, res, next) { if(!req.userInfo.isAdmin) { res.send("Sorry, it's only for Administor!"); return; } // 若是是管理員,繼續下面的操做 next(); });
利用get
請求,獲取後臺頁面,傳入cookie
中的信息req.userInfo
router.get('/', function (req, res, next) { res.render("admin/index.html", { userInfo: req.userInfo }); });
一樣利用get
請求,從數據庫中查詢全部的用戶信息,並將數據返回前端。
分頁展現數據的功能經過:limit()
和skip()
約束實現
倒序經過sort({_id: -1})
約束實現
同時查詢關聯字段的數據經過.populate(['category', 'article'])
實現
router.get('/user', function (req, res, next) { /* 從數據庫中讀取數據,每頁展示的數據數量相同,內容不相同 * 一、limit()約束限制取出數據的條數 * 二、skip()約束限制開始取數據的位置,skip(2)表示忽略前兩天數據,從第三條開始取 * 三、每頁顯示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 // 當前頁數 }); }) }) });
構建分類信息的數據結構和模型
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' }); }); });
經過分類信息展現頁的入口,經過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' }); }) });
文章管理的實現邏輯與分類管理基本一致:
完成文章管理的列表展現頁,有添加文章的入口
// 文章首頁 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' }); }) });
評論能夠單獨存放在一個coolection
中,便於各類操做管理,通用性強,能夠編輯和刪除;增長複雜度
將評論存儲爲文章的一個字段,與一片文章綁定在一塊兒,操做簡便,可是編輯與刪除功能很難實現
經過後端將數據返回以後,服務器端通常將數據構造爲JSON
格式,便於操做,能夠利用後端模板或者前端操做DOM
的方式將數據添加到頁面。
前端渲染:能夠減輕服務器端的開銷,可是首屏的渲染會加長時間
後端渲染:增長服務器的開銷,可是減小客戶端展現的時間