1. 程式人生 > >Node-Blog整套前後端學習記錄

Node-Blog整套前後端學習記錄

express .so reg 優化 outer table表 分享圖片 頁面 選擇

Node-Blog

後端使用node寫的一個一整套的博客系統

#### 主要功能

  • 登錄
  • 註冊
  • 發表文章
  • 編輯/刪除文章
  • 添加/刪除/編輯文章分類
  • 賬號的管理
  • 評論功能
  • ...

所用技術

  • node

  • express
  • swig渲染模板
  • body-parser中間件
  • cookies
  • mongod(mongoose) 數據庫
  • html css js ajax等

主要頁面展示

  • index

    技術分享圖片

  • 詳情頁

    技術分享圖片

    ?

  • 後臺

    技術分享圖片

一、項目初始化

1.1 創建目錄

├─models 存放數據庫數據模型
├─public 存放靜態資源
├─routers 路由文件
├─schemas 數據庫Schema表
└─views 靜態頁面

│ .gitignore github倉庫上傳忽略文件
│ app.js 主程序入口文件
│ package-lock.json
│ package.json
│ README.md

1.2 裝包

使用npm安裝項目要使用的包

1.3 創建基本app服務

var express = require('express')
var mongoose = require('mongoose')

var app = express()

// 連接數據庫
mongoose.connect('mongodb://localhost/node-blog', { useNewUrlParser: true });

app.listen(3000, function () {
  console.log('http://localhost:3000') 
})

二、開發開始

2.1 模板使用 swig

// 定義模板引擎
app.engine('html', swig.renderFile)
// 設置模板文件存放目錄
app.set('views', './views')
// 註冊模板引擎
app.set('view engine', 'html')

//設置swig頁面不緩存
swig.setDefaults({
  allowErrors: false,
  autoescape: true,
  cache: false
})

2.2 靜態文件托管

// 靜態文件托管
app.use('/public', express.static(__dirname + '/public')

知識點1:在 Express 中提供靜態文件

為了提供諸如圖像、CSS 文件和 JavaScript 文件之類的靜態文件,請使用 Express 中的 express.static 內置中間件函數。

app.use(express.static('public'));

這樣後 我們就可以訪問public文件中的任意目錄的任意文件:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js

註意: Express 相對於靜態目錄查找文件,因此靜態目錄的名稱不是此 URL 的一部分

可以多次使用static函數開啟多個靜態資源入口。

自定義文件目錄名稱

上面的例子中我們可以訪問 http://localhost:3000/js/app.js這個目錄 但是如果我想通過http://localhost:3000/static/js/app.js來訪問,我們可以使用:

app.use('/static', express.static('public'));

來創建虛擬路徑前綴(路徑並不實際存在於文件系統中)

當然,在項目中一般使用絕對路徑來保證代碼的可行性:

app.use('/static', express.static(__dirname + '/public'));

2.3 連接數據庫

// 連接數據庫
mongoose.connect('mongodb://localhost/node-blog' { useNewUrlParser: true });

mongod會在第一個數據創建的時候新建我們的node-blog數據庫,不需要我們手動創建

後面的一個配置項最好加上。不報錯的話可不加。

2.4 分模塊開發與實現

路由
  • 前臺模塊 main模塊
    • / 首頁
    • / 內容頁
  • 後臺管理模塊 admin模塊
  • API模塊 api模塊
// 路由
app.use('/admin', require('./routers/admin'))
app.use('/api', require('./routers/api'))
app.use('/', require('./routers/main'))

知識點2:express.Router的使用

使用 express.Router 類來創建可安裝的模塊化路由處理程序。Router 實例是完整的中間件和路由系統;因此,常常將其稱為“微型應用程序”。

使用express.Router,可以將路由更加模塊化

比如:在 routers文件夾下新建 main.js

var express = require('express')
var router = express.Router()
...

router.get('/', function (req, res, next) {
    ...
}

router.get('/view',(req, res) => {
    ...
}
    
module.exports = router

末尾使用module.exports = router 將router對象暴露出去

我們將其安裝在主應用程序app.js的路徑中

...
app.use('/', require('./routers/main'))
...

此時的 ‘/’ 路徑請求的就是 main.js中的 ’/‘

/view --> main.js 中的 ‘/view‘

開發順序

功能模塊開發順序

  • 用戶
  • 欄目
  • 內容
  • 評論

編碼順序

  • Schema 定義存儲結構
  • 功能邏輯
  • 頁面展示

三、註冊 登錄 登出

3.1 userSchema創建

新建並編寫 schemas/user.js

var mongoose = require('mongoose')

// 用戶表結構
module.exports = new mongoose.Schema({
  username: {
    type: String
  },
  password: {
    type: String
  }
})

3.2 創建User model

var mongoose = require('mongoose')
var userSchema = require('../schemas/user')

module.exports = mongoose.model('User', userSchema)

知識點3:mongoose中的 Schema 和 Model

Mongoose 的一切始於 Schema。每個 schema 都會映射到一個 MongoDB collection ,並定義這個collection裏的文檔的構成

關於schema的官方文檔

  • 定義一個schema

      var mongoose = require('mongoose');
      var Schema = mongoose.Schema;
    
      var blogSchema = new Schema({
        title:  String,
        author: String,
        body:   String,
        comments: [{ body: String, date: Date }],
        date: { type: Date, default: Date.now },
        hidden: Boolean,
        meta: {
          votes: Number,
          favs:  Number
        }
      });
  • 創建一個model

    我們要把 schema 轉換為一個 Model, 使用 mongoose.model(modelName, schema) 函數:

      var Blog = mongoose.model('Blog', blogSchema);

    Models 是從 Schema 編譯來的構造函數。 它們的實例就代表著可以從數據庫保存和讀取的 documents。 從數據庫創建和讀取 document 的所有操作都是通過 model 進行的。

    第一個參數是跟 model 對應的集合( collection )名字的 單數 形式。 Mongoose 會自動找到名稱是 model 名字 復數形式的 collection 。 對於上例,Blog這個 model 就對應數據庫中 blogs 這個 collection。.model() 這個函數是對 schema 做了拷貝(生成了 model)。

    你要確保在調用 .model() 之前把所有需要的東西都加進 schema 裏了

    一個model就是創造了一個mongoose實例,我們才能將其操控。

    我的片面理解把Schema和model的關系 想成 構造函數和實例之間的關系

3.3 註冊

註冊邏輯

  • 表單驗證
  • 數據庫驗證
  • 前臺 ajax
  1. 靜態頁面

  2. 處理 前端ajax註冊

        // 註冊
        $register.find('.user_register_btn').on('click', function () {
            $.ajax({
                type: 'post',
                url: 'api/user/register',
                data: {
                    username: $register.find('[name="username"]').val(),
                    password: $register.find('[name="password"]').val(),
                    repassword: $register.find('[name="repassword"]').val()
                },
                dataType: 'json',
                success: function (result) {
                    $register.find('.user_err').html(result.message)
    
                    if (!result.code) {
                        setTimeout(() => {
                            $('.j_userTab span')[0].click()
                        }, 1000)
                    }
                }
            })
        })
  3. 後臺api路由

    在api.js中編寫後臺註冊相關代碼

    /*
    註冊:
      註冊邏輯
      1. 用戶名不能為空
      2. 密碼不能為空
      3. 兩次密碼一致
    
      數據庫查詢
      1. 用戶名是否已經被註冊
    */
    router.post('/user/register', function (req, res, next) {
      var username = req.body.username
      var password = req.body.password
      var repassword = req.body.repassword
    
    // -------表單簡單驗證-----------
      if (username == '') {
        responseData.code = 1
        responseData.message = '不填用戶名啊你'
        res.json(responseData)
        return
      }
      if (password == '') {
        responseData.code = 2
        responseData.message = '密碼不填?'
        res.json(responseData)
        return
      }
      if (password !== repassword ) {
        responseData.code = 3
        responseData.message = '兩次密碼不一致啊'
        res.json(responseData)
        return
      }
    // -------------------------------
    
    // -------數據庫驗證驗證-----------
      User.findOne({
        username: username
      }).then((userInfo) => {
        if (userInfo) {
          // 數據庫中已有用戶
          responseData.code = 4
          responseData.message = '用戶名有了,去換一個'
          res.json(responseData)
          return
        }
        // 保存用戶註冊信息
        var user = new User({
          username: username,
          password: password
        })
        return user.save()
      }).then((newUserInfo) => {
        responseData.message = '耶~ 註冊成功'
        res.json(responseData)
      })
    // -------------------------------
    
    })

    後臺通過簡單的驗證,將結果通過 res.json 的方式來返還給 前臺 ajax 再通過json信息來處理頁面展示。

    ?

    知識點4:使用body-parser中間件來處理post請求

    關於express的更多中間件

    使用案例

    var express = require('express')
    var bodyParser = require('body-parser')
    
    var app = express()
    
    // parse application/x-www-form-urlencoded
    app.use(bodyParser.urlencoded({ extended: false }))
    
    // parse application/json
    app.use(bodyParser.json())

    通過以上的配置,我們就可以獲取通過 req.body 來獲取 post 請求總的參數了

    ...
      var username = req.body.username
      var password = req.body.password
      var repassword = req.body.repassword
    ...

    知識點5: mongoose中數據庫的操作

    前段時間總結過一些mongoose的增刪查操作筆記:

    ? node中的mongodb和mongoose

    ?

#### 3.4 登錄

  1. 前臺ajax

    // 登錄
        $login.find('.user_login_btn').on('click', function () {
            $.ajax({
                type: 'post',
                url: 'api/user/login',
                data: {
                    username: $login.find('[name="username"]').val(),
                    password: $login.find('[name="password"]').val(),
                },
                dataType: 'json',
                success: function (result) {
                    $login.find('.user_err').html(result.message)
                    // 登錄成功
                    if (!result.code) {
                        window.location.reload()
                    }
                }
            })
        })
  2. 後臺路由處理及數據庫查詢

    // 登錄邏輯處理
    router.post('/user/login', (req, res) => {
      var username = req.body.username
      var password = req.body.password
      if (username == '' || password == '') {
        responseData.code = 1
        responseData.message = '去填完再點登錄'
        res.json(responseData)
        return
      }
    
    // 查詢數據庫用戶名密碼同時存在
      User.findOne({
        username: username,
        password: password
      }).then((userInfo) => {
        if (!userInfo) {
          responseData.code = 2
          responseData.message = '用戶名或密碼錯啦'
          res.json(responseData)
          return
        }
        // 正確 登錄成功
        responseData.message = '耶~ 登錄成功'
        responseData.userInfo = {
          _id: userInfo._id,
          username: userInfo.username
        }
        req.cookies.set('userInfo', JSON.stringify({
          _id: userInfo._id,
          username: escape(userInfo.username)
        }))
        res.json(responseData)
      })
    })

3.5 cookies

上面的案例中,為了記錄我們的登錄狀態,我們使用了第三發包 -- cookies 來存儲登錄信息

  1. app 引入 cookies模塊

    var Cookies = require('cookies')
  2. 在 api.js 中獲取 cookies

    req.cookies.set('userInfo', JSON.stringify({
          _id: userInfo._id,
          username: escape(userInfo.username)
        }))

    ?

  3. 在 app.js 中解析登錄用戶的cookies

    // 設置cookies
    app.use((req, res, next) => {
      req.cookies = new Cookies(req, res)
    
      // 解析登錄用戶的cookies
      req.userInfo = {}
      if (req.cookies.get('userInfo')) {
        try {
          req.userInfo = JSON.parse(req.cookies.get('userInfo'))
    
          // 獲取用戶是否是管理員
          User.findById(req.userInfo._id).then((userInfo) => {
            req.userInfo.isAdmin = Boolean(userInfo.isAdmin)
            next()
          })
        } catch (e) {
          next()
        }
      } else {
        next()
      }
    }
  4. 用 swig 渲染模板控制 index頁面

3.6登出

ajax --》 api.js --> cookies設置為空 -> 刷新頁面

登出的實現就比較簡單,只需將cookies設置為空即可

  1. 前臺ajax

        // 登出
        $('#logout').on('click', function () {
            $.ajax({
                url: '/api/user/logout',
                success: function(result) {
                    if (!result.code) {
                        window.location.reload()
                    }
                }
            })
        })
  2. api路由

    // 退出登錄
    router.get('/user/logout', (req, res) => {
      req.cookies.set('userInfo', null)
      res.json(responseData)
    })

3.7 中文用戶名登錄異常

原因 cookies在存儲中午時出現亂碼
解決辦法 將username進行轉碼再解碼

使用 encodedecode 來進 編碼和解碼

3.8 區分管理員

給userInfo 添加 isAdmin 屬性

使用swig 選擇渲染

四、後臺管理

4.1 bootstrap 模板建立頁面

4.2 使用繼承模板

公用的繼承

{% extends 'layout.html' %}

特殊的重寫

{% block main %}
  <div class="jumbotron">
    <h1>Hello, {{userInfo.username}}!</h1>
    <p>歡迎進入後臺管理</p>
  </div>
{% endblock%}

admin 首頁

// 首頁
router.get('/', (req, res, next) => {
  res.render('admin/index', {
    userInfo: req.userInfo
  })
})

4.3 admin/user 用戶管理

  • 建立靜態 user_index.html

  • 處理路由及分頁邏輯

    // 用戶管理
    router.get('/user', (req, res) => {
    
      /*
      從數據庫中讀取所有的用戶數據
       limit(number) 限制獲取的數據條數
       skip(number) 忽略數據的條數
        每頁顯示 5 條
        第一頁: 1-5  skip:0  -> (當前頁 1 - 1) * 每頁的條數
        第二頁: 6-10 skip:5  -> (當前頁 2 - 1) * 每頁的條數
        ...
        ...
        User.count() 查詢總數據量
       */
      var page = Number(req.query.page || 1)
      var pages = 0
      var limit = 10
    
      User.count().then((count) => {
        // 計算總頁數
        pages = Math.ceil(count / limit)
        // 取值不能超過 pages
        page = Math.min(page, pages)
        // 取值不能小於1
        page = Math.max(page, 1)
        var skip = (page - 1) * limit
    
        // 讀取數據庫中所有用戶數據
        User.find().limit(limit).skip(skip).then((users) => {
          res.render('admin/user_index', {
            userInfo: req.userInfo,
            users: users,
            page: page,
            pages: pages,
            count: count,
            limit: limit
          })
        })
      })
    
    })
  • 頁面展示 --table表格

  • 分頁

    • 數據裏 limit

    • skip()

    • 分頁原理

        /*
        從數據庫中讀取所有的用戶數據
         limit(number) 限制獲取的數據條數
         skip(number) 忽略數據的條數
          每頁顯示 5 條
          第一頁: 1-5  skip:0  -> (當前頁 1 - 1) * 每頁的條數
          第二頁: 6-10 skip:5  -> (當前頁 2 - 1) * 每頁的條數
          ...
          ...
         */
        var page = req.query.page || 1
        var limit = 5
        var skip = (page - 1) * limit
        User.find().limit(limit).skip(skip).then((users) => {
          res.render('admin/user_index', {
            userInfo: req.userInfo,
            users: users
          })
        })
    • 客戶端實現

        <nav aria-label="...">
          <ul class="pager">
            <li class="previous"><a href="/admin/user?page={{page-1}}"><span aria-hidden="true">&larr;</span>上一頁</a></li>
            <li>
              一共有 {{count}} 條數據 || 每頁顯示 {{limit}} 條數據 || 一共 {{pages}} 頁 || 當前第 {{page}} 頁
            </li>
            <li class="next"><a href="/admin/user?page={{page+1}}">下一頁<span aria-hidden="true">&rarr;</span></a></li>
          </ul>
        </nav>
    • 服務端代碼

      /*
        從數據庫中讀取所有的用戶數據
         limit(number) 限制獲取的數據條數
         skip(number) 忽略數據的條數
          每頁顯示 5 條
          第一頁: 1-5  skip:0  -> (當前頁 1 - 1) * 每頁的條數
          第二頁: 6-10 skip:5  -> (當前頁 2 - 1) * 每頁的條數
          ...
          ...
          User.count() 查詢總數據量
         */
      
        var page = Number(req.query.page || 1)
        var pages = 0
        var limit = 5
      
        User.count().then((count) => {
          // 計算總頁數
          pages = Math.ceil(count / limit)
          // 取值不能超過 pages
          page = Math.min(page, pages)
          // 取值不能小於1
          page = Math.max(page, 1)
          var skip = (page - 1) * limit
      
          User.find().limit(limit).skip(skip).then((users) => {
            res.render('admin/user_index', {
              userInfo: req.userInfo,
              users: users,
              page: page,
              pages: pages,
              count: count,
              limit: limit
            })
          })
        })
    • 抽取page 使用 include 語法以後復用

4.4 文章分類相關

  1. 分類首頁

    category_index.html

  2. 添加分類

    category_add.html

    • get 渲染頁面

    • post 提交頁面

    • 設計表結構
      schemas/categories.js
      models/categories.js

    • 相關代碼

/*
添加分類頁面 
 */
router.get('/category/add', (req, res) => {
  res.render('admin/category_add', {
    userInfo: req.userInfo
  })
})

/*
添加分類的保存
 */
router.post('/category/add', (req, res) => {
  var name = req.body.name || ''
  if (name == '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '名稱不能為空'
    })
    return
  }

  // 是否已有分類
  Category.findOne({
    name: name
  }).then((result) => {
    if (result) {
      // 數據庫中已經存在
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分類已經存在'
      })
      return Promise.reject()
    } else {
      // 數據庫中不存在分類
      return new Category({
        name: name
      }).save()
    }
  }).then((newCategory) => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分類保存成功',
      url: '/admin/category'
    })
  })
})

通過判斷 渲染 error 或者 success 的頁面 兩個頁面都在 admin/error.htmladmin/success.html

  1. 首頁展示展示

    同用戶管理首頁展示一樣

/*
分類首頁 
 */
router.get('/category', (req, res) => {
  var page = Number(req.query.page || 1)
  var pages = 0
  var limit = 10

  Category.count().then((count) => {
    // 計算總頁數
    pages = Math.ceil(count / limit)
    // 取值不能超過 pages
    page = Math.min(page, pages)
    // 取值不能小於1
    page = Math.max(page, 1)
    var skip = (page - 1) * limit

    Category.find().limit(limit).skip(skip).then((categories) => {
      res.render('admin/category_index', {
        userInfo: req.userInfo,
        categories: categories,
        page: page,
        pages: pages,
        count: count,
        limit: limit
      })
    })
  })
})
  1. 分類修改 刪除

    在渲染的分類首頁的分類表格中加入

      <td>
        <a href="/admin/category/edit?id={{category._id.toString()}}" class="btn btn btn-primary">修改</a>
        <a href="/admin/category/delete?id={{category._id.toString()}}" class="btn btn-danger">刪除</a>
      </td>

通過query的傳值分類的id 值 我們來操作id

  • 修改

    get

    /* 
     分類修改 get
     */
    router.get('/category/edit', (req, res) => {
      // 獲取要修改的分類信息 表單形式展現出來
    
      var id = req.query.id || ''
      // 獲取修改的分類信息
      Category.findById(id).then((category) => {
        if (!category) {
          res.render('admin/error', {
            userInfo: req.userInfo,
            message: '分類信息不存在'
          })
          return Promise.reject()
        } else {
          res.render('admin/category_edit', {
            userInfo: req.userInfo,
            category: category
          })
        }
      })
    })

    post

    /* 
     分類修改 post
     */
    router.post('/category/edit', (req, res) => {
      var id = req.query.id || ''
      var name = req.body.name || ''
    
      Category.findById(id).then((category) => {
        if (!category) {
          res.render('admin/error', {
            userInfo: req.userInfo,
            message: '分類信息不存在'
          })
          return Promise.reject()
        } else {
          // 當前用戶沒有做任何修改而提交
          if (name == category.name) {
            res.render('admin/success', {
              userInfo: req.userInfo,
              message: '修改成功',
              url: '/admin/category'
            })
            return Promise.reject()
          } else {
            // 要修改的分類名稱是否已經在數據庫中
            return Category.findOne({
              // id 不等於當前的id
              _id: {$ne: id},
              name: name
            })
          }
        }
      }).then((sameCategory) => {
        if (sameCategory) {
          res.render('admin/error', {
            userInfo: req.userInfo,
            message: '已存在同名分類'
          })
          return Promise.reject()
        } else {
          return Category.findByIdAndUpdate(id, {
            name: name
          })
        }
      }).then(() => {
        res.render('admin/success', {
          userInfo: req.userInfo,
          message: '修改分類名稱成功',
          url: '/admin/category'
        })
      })
    })
  • 刪除

    /* 
     分類刪除
     */
    router.get('/category/delete', (req, res) => {
      // 獲取id
      var id = req.query.id || ''
    
      Category.remove({
        _id: id
      }).then(() => {
        res.render('admin/success', {
          userInfo: req.userInfo,
          message: '刪除成功',
          url: '/admin/category'
        })
      })
    })

    ?

4.5 內容管理 -內容首頁和內容添加

/* 
內容首頁
 */
router.get('/content', (req, res) => {
  res.render('admin/content_index', {
    userInfo: req.userInfo
  })
})

/* 
內容添加
 */
router.get('/content/add', (req, res) => {

  Category.find().sort({_id: -1}).then((categories) => {
    console.log(categories)
    res.render('admin/content_add', {
      userInfo: req.userInfo,
      categories: categories
    })
  })
})

4.6內容提交保存

  • 新建 schemas/content.js 和 models/content.js 建立content模型

  • 處理路由

    post

    後臺

       // 保存內容到數據庫
       new Content({
         category: req.body.category,
         title: req.body.title,
         description: req.body.description,
         content: req.body.content
       }).save().then((content) => {
         res.render('admin/success', {
           userInfo: req.userInfo,
           message: '內容保存成功',
           url: '/admin/content'
         })
       })
     })

4.7 關於內容分類的表關聯關系

module.exports = new mongoose.Schema({
  title: {
    type: String
  },

  // 引用 關聯字段
  category: {
    type: mongoose.Schema.Types.ObjectId,
    //引用 另外一張表的模型
    ref: 'Category'
  },

  description: {
    type: String,
    default: ''
  },
  content: {
    type: String,
    default: ''
  }
})

我們在 處理 content 的 category的時候 關聯個 另外一個結構表

在渲染頁面的時候用mongoose 中提供搞得 populate() 方法

知識點6: mongoose中的表關聯

Population 可以自動替換 document 中的指定字段,替換內容從其他 collection 獲取。 我們可以填充(populate)單個或多個 document、單個或多個純對象,甚至是 query 返回的一切對象

簡單的說,A表的可以關聯B表,通過調用A表的屬性數據取到B表內容的值,就像sql的join的聚合操作一樣。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var personSchema = Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  age: Number,
  stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  author: { type: Schema.Types.ObjectId, ref: 'Person' },
  title: String,
  fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});

var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

我們創建了Story 和 Person兩個數據庫實例。

Person model 的 stories 字段設為 ObjectId數組。 ref 選項告訴 Mongoose 在填充的時候使用哪個 model,本例中為 Story model。

接下來我們使用 Population 來填充使用

Story.
  findOne({ title: 'Casino Royale' }).
  populate('author').
  exec(function (err, story) {
    if (err) return handleError(err);
    console.log('The author is %s', story.author.name);
    // prints "The author is Ian Fleming"
  });

更多高級用法: Mongoose Populate

4.8 內容修改

/*
 修改內容 
  */
router.get('/content/edit', (req, res) => {
  // 獲取要修改的內容信息 表單形式展現出來

  var id = req.query.id || ''

  var categories = []
  // 獲取分類信息
  Category.find().sort({ _id: -1 })
  .then((result) => {
    categories = result
    return Content.findById(id).populate('category')
  })
  .then((content) => {
    console.log(content)
    if (!content) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '指定內容不存在'
      })
      return Promise.reject()
    } else {
      res.render('admin/content_edit', {
        userInfo: req.userInfo,
        content: content,
        categories: categories
      })
    }
  })

4.9 內容保存

/*
   內容修改
   */
  router.post('/content/edit', function(req, res) {
    var id = req.query.id || ''
    
    if (req.body.title == '') {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '標題不能為空'
      })
      return
    }

    if (req.body.description == '' || req.body.content == '') {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '簡介和內容不能為空'
      })
      return
    }

    Content.findByIdAndUpdate(id, {
      category: req.body.category,
      title: req.body.title,
      description: req.body.description,
      content: req.body.content
    }).then(() => {
      res.render('admin/success', {
        userInfo: req.userInfo,
        message: '內容保存成功',
        url: '/admin/content'
      })
    })

  })
  
})

4. 10內容刪除

/* 
內容刪除
*/
router.get('/content/delete', (req, res) => {
  // 獲取id
  var id = req.query.id || ''

  Content.remove({
    _id: id
  }).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '刪除成功',
      url: '/admin/content'
    })
  })
})

添加一些文章 信息 -- 作者 創建時間 點擊量

作者 -- 關聯 user表

創建時間 -- new Date()

? 前臺渲染

<td>{{content.addTime|date('Y-m-d H:i:s', -8*60)}}</td>

點擊量 --》 先默認為 0

五、前臺相關

有了後臺的數據,我們接下來看前臺的

修改 main.js

/*
首頁渲染 
 */
router.get('/', function (req, res, next) {
  req.userInfo.username = unescape(req.userInfo.username)

  var data = {
    userInfo: req.userInfo,
    categories: [],
    contents: [],
    count: 0,
    page : Number(req.query.page || 1),
    pages : 0,
    limit : 10
  }
  
  Category.find()
    .then((categories) => {
      data.categories = categories
      return Content.count()
  })
    .then((count) => {
      data.count = count
      // 計算總頁數
      data.pages = Math.ceil(data.count / data.limit)
      // 取值不能超過 pages
      data.page = Math.min(data.page, data.pages)
      // 取值不能小於1
      data.page = Math.max(data.page, 1)
      var skip = (data.page - 1) * data.limit

      return Content
        .find()
        .sort({ addTime: -1 })
        .limit(data.limit)
        .skip(skip)
        .populate(['category', 'user'])
    })
    .then((contents) => {
      data.contents = contents
      console.log(data)
      res.render('main/index', data)
    })
})

5.1 完善首頁細節 改為後臺傳來的data顯示

使用swig的渲染模板 完善頁面信息,不在贅述

5.2 設置分頁

{% if pages > 1 %}
<nav aria-label="..." id="pager_dh">
  <ul class="pager">
    {% if page <=1 %} <li class="previous"><span href="#"><span aria-hidden="true">&larr;</span>沒有上一頁了</span></li>
      {%else%}
      <li class="previous"><a href="/?category={{category}}&page={{page-1}}"><span aria-hidden="true">&larr;</span>上一頁</a></li>
      {%endif%}
      <span class="page_text">{{page}} / {{pages}}</span>
      {% if page >=pages %}
      <li class="next"><span href="#">沒有下一頁了<span aria-hidden="true">&rarr;</span></li>
      {%else%}
      <li class="next"><a href="/?category={{category}}&page={{page+1}}">下一頁<span aria-hidden="true">&rarr;</span></a></li>
      {%endif%}
  </ul>
</nav>
{%endif%}

5.3 content 創建時間的問題

我們創建addTime的時候,會發現mongod創建的數據的時間戳完全一樣

我們不能使用new date()來創建默認時間 使用 Date.now

5.4 處理分類點擊跳轉

  var where = {}
  if (data.category) {
    where.category = data.category
  }

mongoose查詢的時候使用 where 查詢

5.5 分類高亮顯示

      <nav class="head_nav">
        {% if category == ''%}
        <a href="/" id="inactive">首頁</a>
        {%else%}
        <a href="/">首頁</a>
        {%endif%}

        {% for cate in categories%}
          {% if category == cate.id%}
          <a href="/?category={{cate.id}}" id="inactive">{{cate.name}}</a>
          {%else%}
          <a href="/?category={{cate.id}}">{{cate.name}}</a>
          {%endif%}
        {% endfor %}
      </nav>

5.6 評論相關

評論使用ajax來操作

使用ajax操作不刷新頁面來操作api

後臺api代碼

/*
進入詳情獲取評論
 */
router.get('/comment/post', (req, res) => {
  var contentid = req.query.contentid
  Content.findById(contentid)
    .then((content) => {
      responseData.data = content.comments
      res.json(responseData)
    })
})

/*
評論提交 
 */
router.post('/comment/post', (req, res) => {
  var contentid = req.body.contentid
  var postData = {
    username: req.userInfo.username,
    postTime: Date.now(),
    content: req.body.content
  }

  // 查詢文章內容信息
  Content.findById(contentid)
    .then((content) => {
      content.comments.push(postData)
      return content.save()
    })
    .then((newContent) => {
      responseData.message = '評論成功!'
      responseData.data = newContent
      res.json(responseData)
    })
})

評論代碼

ajax的操作都封裝在了 routers/api.js 中

評論相關操作我們都放在了js/comments.js 中

var limit = 4
var page = 1
var pages = 0
var comments = []

// 加載所有評論
$.ajax({
  type: 'get',
  url: 'api/comment/post',
  data: {
    contentid: $('#contentId').val(),
  },
  success: ((responseData) => {
    comments = responseData.data
    renderComment()
  })
})

$('.pager').delegate('a', 'click', function() {
  if ($(this).parent().hasClass('previous')) {
    page--
  } else {
    page++
  }
  renderComment()
})


// 提交評論
$('#commentBtn').on('click',function() {
  $.ajax({
    type: 'post',
    url: 'api/comment/post',
    data: {
      contentid: $('#contentId').val(),
      content: $('#commentContent').val()
    },
    success: ((responseData) => {
      $('#commentContent').val('')
      comments = responseData.data.comments
      renderComment(true)
    })
  })
})

function renderComment (toLaster) {
  $('#discuss_count').html(comments.length)

  var $lis = $('.pager li')
  pages = Math.ceil(comments.length / limit)
  if (!toLaster) {
    var start = (page-1) * limit
  } else {
    var start = (pages - 1) * limit
    page = pages
  }
  var end = (start + limit) > comments.length ? comments.length : (start + limit)
  if (pages <= 1) {
    $('.pager').hide()
  } else {
    $('.pager').show()
    $lis.eq(1).html(page + '/' + pages )
  
    if (page <= 1) {
      page = 1
      $lis.eq(0).html('<span>已是最前一頁</span>')
    } else {
      $lis.eq(0).html('<a href="javacript:void(0);">上一頁</a>')
    }
  
    if (page >= pages) {
      page = pages
      $lis.eq(2).html('<span>已是最後一頁</span>')
    } else {
      $lis.eq(2).html('<a href="javacript:void(0);">下一頁</a>')
    }
  }


  var html = ''
  if (comments.length) {
    for (var i = start; i < end; i++) {
      html += `
        <li>
            <p class="discuss_user"><span>${comments[i].username}</span><i>發表於 ${formatDate(comments[i].postTime)}</i></p>
            <div class="discuss_userMain">
                ${comments[i].content}
            </div>
        </li>
      `
    }
  }

  $('.discuss_list').html(html)
}

function formatDate(d) {
  var date1 = new Date(d)
  return date1.getFullYear() + '年' + (date1.getMonth()+1) + '月' + date1.getDate() + '日' + date1.getHours() + ':' + date1.getMinutes() + ':' + date1.getSeconds()
}

六、總結

項目這個階段知識簡單能跑痛而已,包括細節的優化,和程序的安全性都沒有考慮,安全防範措施為零,這也是以後要學習的地方。

第一次使用node寫後臺,完成了一次前後端的完整交互,最終要的還是做後臺的一種思想,一種處理前後臺關系的邏輯。

收獲了很多,越來越感覺自己要學的東西太多了,自己好菜。。

寫總結文檔有點累唉 _(°:з」∠)_禿頭。

Node-Blog整套前後端學習記錄