Node-Blog整套前後端學習記錄
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
靜態頁面
處理 前端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) } } }) })
後臺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 登錄
前臺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() } } }) })
後臺路由處理及數據庫查詢
// 登錄邏輯處理 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 來存儲登錄信息
app 引入 cookies模塊
var Cookies = require('cookies')
在 api.js 中獲取 cookies
req.cookies.set('userInfo', JSON.stringify({ _id: userInfo._id, username: escape(userInfo.username) }))
?
在 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() } }
用 swig 渲染模板控制 index頁面
3.6登出
ajax --》 api.js --> cookies設置為空 -> 刷新頁面
登出的實現就比較簡單,只需將cookies設置為空即可
前臺ajax
// 登出 $('#logout').on('click', function () { $.ajax({ url: '/api/user/logout', success: function(result) { if (!result.code) { window.location.reload() } } }) })
api路由
// 退出登錄 router.get('/user/logout', (req, res) => { req.cookies.set('userInfo', null) res.json(responseData) })
3.7 中文用戶名登錄異常
原因 cookies在存儲中午時出現亂碼
解決辦法 將username進行轉碼再解碼
使用 encode
和 decode
來進 編碼和解碼
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">←</span>上一頁</a></li> <li> 一共有 {{count}} 條數據 || 每頁顯示 {{limit}} 條數據 || 一共 {{pages}} 頁 || 當前第 {{page}} 頁 </li> <li class="next"><a href="/admin/user?page={{page+1}}">下一頁<span aria-hidden="true">→</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 文章分類相關
分類首頁
category_index.html
添加分類
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.html
和 admin/success.html
中
首頁展示展示
同用戶管理首頁展示一樣
/*
分類首頁
*/
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
})
})
})
})
分類修改 刪除
在渲染的分類首頁的分類表格中加入
<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">←</span>沒有上一頁了</span></li>
{%else%}
<li class="previous"><a href="/?category={{category}}&page={{page-1}}"><span aria-hidden="true">←</span>上一頁</a></li>
{%endif%}
<span class="page_text">{{page}} / {{pages}}</span>
{% if page >=pages %}
<li class="next"><span href="#">沒有下一頁了<span aria-hidden="true">→</span></li>
{%else%}
<li class="next"><a href="/?category={{category}}&page={{page+1}}">下一頁<span aria-hidden="true">→</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整套前後端學習記錄