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

Node-Blog

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

#### 主要功能css

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

所用技術

  • nodehtml

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

主要頁面展現

  • indexjava

    首頁

  • 詳情頁node

    詳情頁

  • 後臺git

    後臺管理

1、項目初始化

1.1 建立目錄

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

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

1.2 裝包

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

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、開發開始

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、註冊 登陸 登出

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、後臺管理

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

5、前臺相關

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

修改 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()
}

6、總結

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

第一次使用node寫後臺,完成了一次先後端的完整交互,最終要的仍是作後臺的一種思想,一種處理先後臺關係的邏輯。

收穫了不少,愈來愈感受本身要學的東西太多了,本身好菜。。

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

相關文章
相關標籤/搜索