Node-Blog整套前后端学习记录

时间:2022-07-29 08:26:20

Node-Blog

后端使用node写的一个一整套的博客系统

主要功能

  • 登录
  • 注册
  • 发表文章
  • 编辑/删除文章
  • 添加/删除/编辑文章分类
  • 账号的管理
  • 评论功能
  • ...

所用技术

  • node

  • express

  • swig渲染模板

  • body-parser中间件

  • cookies

  • mongod(mongoose) 数据库

  • html css js ajax等

主要页面展示

  • index

    Node-Blog整套前后端学习记录

  • 详情页

    Node-Blog整套前后端学习记录

  • 后台

    Node-Blog整套前后端学习记录

一、项目初始化

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写后台,完成了一次前后端的完整交互,最终要的还是做后台的一种思想,一种处理前后台关系的逻辑。

收获了很多,越来越感觉自己要学的东西太多了,自己好菜。。

写总结文档有点累唉 _(°:з」∠)_秃头。