node express mongoDB我的博客總結

1 註冊、登陸和退出

1.1 用戶註冊、登陸

  1. 配置模板引擎、mongoDB數據庫驅動、靜態文件路徑和post請求解析中間件html

  2. 統一api.js路由的數據返回格式前端

    // 統一返回數據格式
    var responseData;
    // 每次請求進來都進行初始化
    router.use(function (res, req, next) {
      responseData = {
        code: 0,     // 狀態碼,默認爲0
        message: ''   //  狀態碼的提示信息,默認爲0
      };
    
      next();   // 調用next()交由下一個中間件繼續處理
    });
  3. 設計用戶的數據模型設計與建立數據庫

    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;
  4. 完成註冊邏輯後端

    • 前端將數據提交到指定路由(Ajax或整頁刷新)api

    • 服務器獲取提交的數據,進行基本驗證與數據庫查重驗證瀏覽器

    • 若是數據庫中用戶名已經存在,返回錯誤信息;若是不存在,則保存當前註冊信息緩存

  5. 完成登陸邏輯服務器

    • 前端利將數據提交到指定路由(Ajax或整頁刷新)cookie

    • 服務器經過body-parser中間件獲取post請求中的數據req.body;經過req.query獲取get請求中的數據,進行基本驗證數據結構

    • 進行數據庫查詢驗證:使用usernamepassword兩個字段進行查詢,若是存在,則返回登陸成功;不然登陸失敗

    • 同時,爲登陸成功的用戶發送一個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) {
    /* 從數據庫中讀取數據,每頁展示的數據數量相同,內容不相同
     *   一、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     // 當前頁數
    });
      })
    })
    });

2.4 分類信息的添加

  1. 構建分類信息的數據結構和模型

    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;
  2. 完成get路由獲取展現分類的頁面(包括修改和刪除分類的入口):經過數據庫查詢獲取全部的分類信息,一樣利用limit()skip()實現分頁

    Category.find().sort({_id: -1}).limit(limit).skip(skip).then(function (categories){...}
  3. 完成get路由獲取分類添加的頁面,利用post請求提交數據

  4. 後端經過post路由獲取添加分類的數據,進行基本驗證與數據庫查重:若是經過,則保存數據;不然返回錯誤信息。利用findOne()方法查詢一條記錄;

  5. 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請求將修改的數據進行更新

  1. 前端經過url傳入修改和刪除分類的_id

  2. 經過數據庫查詢到該條記錄,返回原有數據,渲染到編輯分類頁面,展現原來的分類名稱

  3. 分類修改後,利用post請求上傳數據:_id和修改內容

  4. 後臺拿到數據後,先進行基本驗證;數據庫驗證(查詢分類名是否已經存在)

    Category.findOne({
          id: {$ne: id},    // 不一樣的記錄中是否存在相同的分類名稱
          name: nameCategory
        })
  5. 若是分類名不存在,再利用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'
              });
            })
          }
        })
      }
      }
    })
    });
  6. 數據庫的刪除只需使用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 文章管理

文章管理的實現邏輯與分類管理基本一致:

  1. 完成文章管理的列表展現頁,有添加文章的入口

    // 文章首頁
    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
    });
      });
    });
    });
  2. 完成文章添加頁的表單,經過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
      });
    });
    });
  3. 後端解析獲取文章的數據,進行基本驗證,經過後進行數據保存

    // 文章內容保存路由
    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'
      });
    })
    });
  4. 文章的修改有兩個步驟:首先是獲取文章的編輯頁,將原來的內容渲染到頁面上,編輯修改後,將數據提交到後臺;後臺完成基本驗證後,更新數據庫中的內容

    // 文章內容修改的數據提交
    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'
      })
    });
    });
  5. 文章的刪除與分類刪除一致:使用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 評論的管理

  1. 評論能夠單獨存放在一個coolection中,便於各類操做管理,通用性強,能夠編輯和刪除;增長複雜度

  2. 將評論存儲爲文章的一個字段,與一片文章綁定在一塊兒,操做簡便,可是編輯與刪除功能很難實現

3 前臺展現

經過後端將數據返回以後,服務器端通常將數據構造爲JSON格式,便於操做,能夠利用後端模板或者前端操做DOM的方式將數據添加到頁面。

  • 前端渲染:能夠減輕服務器端的開銷,可是首屏的渲染會加長時間

  • 後端渲染:增長服務器的開銷,可是減小客戶端展現的時間

相關文章
相關標籤/搜索