Node.js仿知乎服务端-深入理解RESTful API202

2020-01-13  本文已影响0人  且须文雅

课程地址: https://coding.imooc.com/learn/list/354.html

跟着GitHub认识RESTful API

REST是什么?

为何叫REST?

通过REST的6个限制详细了解他

1. 客户端-服务器(Client-Server)

2. 无状态

3. 缓存(Cache)

4. 统一接口(Uniform Interface)

5. 分层系统(Layered System)

6. 按需代码(Code-OnDemand 可选)

统一接口的限制

1. 资源的标识

2. 通过表述来操作资源

自描述信息

超媒体作为应用状态引擎

RESTful API简介

什么是RESTful API?

RESTful API具体什么样子?

显示举例

RESTful API设计最佳实践

请求设计规范

响应设计规范

安全

开发者友好

用Koa 说 Hello World

Koala 简介

一句话简介

官网简介

安装搭建第一个Koa程序

安装nodemon包,可以自动重启服务
nodemon index.js

Koa 中间件与洋葱模型

操作步骤

路由简介

路由是什么?

如果没有路由

路由存在的意义

自己编写 Koa 路由中间件

操作步骤

app.use(async (ctx, next) => {
  if (ctx.url === '/') {
    ctx.body = '<h1>这是主页</h1>'
  } else if (ctx.url === '/users') {
    if (ctx.method === 'GET') {
      ctx.body = '这是用户列表页'
    } else if (ctx.method === 'POST') {
      ctx.body = '创建用户'
    } else {
      ctx.status = 405
    }
  } else if (ctx.url.match(/\/users\/\w+/)) {
    const userId = ctx.url.match(/\/users\/(\w+)/)[1]
    ctx.body = `这是用户${userId}`
  } else {
    ctx.status = 404
  }
})

使用 koa-router 实现路由

操作步骤

const usersRouter = new Router({ prefix: '/users' })

const auth = (ctx, next) => {
  if (ctx.url === '/users') {
    ctx.throw(401)
  }
  next()
}

router.get('/', auth, (ctx) => {
  ctx.body = '<h1>这是主页</h1>'
})

usersRouter.get('/', auth, ctx => {
  ctx.body = '这是用户列表页'
})
usersRouter.post('/', auth, ctx => {
  ctx.body = '创建用户'
})

usersRouter.get('/:id', auth, ctx => {
  ctx.body = `这是用户${ctx.params.id}`
})

app.use(router.routes())
app.use(usersRouter.routes())

HTTP options 方法的作用是什么?

为何要了解 options 方法的作用?

HTTP options 方法的作用是什么?

allowedMethods 的作用

app.use(usersRouter.allowedMethods())

RESTful API 最佳实践——增删改查应该返回什么响应

操作步骤

控制器简介

什么是控制器?

为什么要用控制器

获取HTTP请求参数

发送HTTP请求

编写控制器最佳实践

获取HTTP请求参数

操作步骤

发送HTTP响应

操作步骤

更合理的目录结构

操作步骤

错误处理简介

什么是错误处理?

异常状况有哪些?

为什么要用错误处理?

Koa 自带的错误处理

操作步骤

自己编写错误处理中间件

操作步骤

使用 koa-json-error 进行错误处理

操作步骤

const error = require('koa-json-error')
app.use(error({
postFormat: (e, { stack, ...rest }) => {
  process.env.NODE_ENV === 'production' ? rest : { stack, rest }
}
}))

使用 koa-parameter 校验参数

操作步骤

第一批用户入库啦~~

NoSQL 简介

什么是 NoSQL?

NoSQL 数据库的分类

为什么要用NoSQL?

MongoDB 简介

什么是MongoDB?

为什么要用MongoDB?

MongoDB 下载

云 MongoDB

云数据库——MongoDB Atlas

操作步骤

使用 Mongoose 连接 MongoDB

操作步骤

设计用户模块的 Schema

操作步骤

const mongoose = require('mongoose')

const { Schema, model } = mongoose;

const userSchema = new Schema({
  name: {
    type: String,
    required: true
  }
})

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

用MongoDB 实现用户的增删改查

操作步骤

JWT 在 Koa 框架中实现用户的认证与授权

Session 简介

工作原理

Session 工作原理

Session 的优势

Session 的劣势

Session 相关的概念介绍

JWT 简介

什么是 JWT?

JWT 的构成

JWT的例子

Header

Header 编码前后

base64 编码

Payload

Payload 编码前后

Signature

JWT vs. Session

在Node.js中使用JWT

实现用户注册

操作步骤

实现登陆并获取 Token

操作步骤

自己编写 Koa 中间件实现用户认证与授权

操作步骤

用 koa-jwt 中间件实现用户认证与授权

操作步骤

项目实战之上传图片模块

上传图片的需求场景

上传图片的功能点

上传图片的技术方案

使用 koa-body 中间件获取上传的文件

操作步骤

使用 koa-static 中间件生成图片链接

操作步骤

编写前端页面上传文件

操作步骤

项目实战之个人资料模块 —— 学习处理复杂数据类型

个人资料需求分析

个人资料功能点

个人资料的 schema 设计

操作步骤

个人资料的参数校验

操作步骤

RESTful API 最佳实践——字段过滤

操作步骤

关注与粉丝需求分析

浏览知乎的关注与粉丝功能

细化关注与粉丝功能点

关注与粉丝的 schema 设计

操作步骤

following: {
  type: [{ type: Schema.Types.ObjectId, ref: 'User' }],
  select: false
}

RESTful 风格的关注与粉丝接口

操作步骤

async listFollowing (ctx) {
  const user = await User.findById(ctx.params.id).select('+following').populate('following')
  if (!user) {
    ctx.throw(404)
  }
  ctx.body = user.following
}
async listFollowers (ctx) {
  const users = await User.find({ following: ctx.params.id })
  ctx.body = users
}
async follow (ctx) {
  const me = await User.findById(ctx.state.user._id).select('+following')
  if (!me.following.map(id => id.toString()).includes(ctx.params.id)) {
    me.following.push(ctx.params.id)
    me.save()
  }
  ctx.status = 204
}
async unfollow (ctx) {
  const me = await User.findById(ctx.state.user._id).select('+following')
  const index = me.following.map(id => id.toString()).indexOf(ctx.params.id)
  if (index > -1) {
    me.following.splice(index, 1)
    me.save()
  }
  ctx.status = 204
}

编写校验用户存在与否的中间件

操作步骤

// controllers/users.js
async checkUserExist (ctx, next) {
  const user = await User.findById(ctx.params.id)
  if (!user) {
    ctx.throw(404, '用户不存在!')
  }
  await next()
}

项目实战之话题模块(足够完整!!)

话题模块需求分析

浏览知乎的话题模块功能

话题模块功能点

RESTful 风格的话题增改查接口

操作步骤

// models/topics.js
const mongoose = require('mongoose')

const { Schema, model } = mongoose;

const topicSchema = new Schema({
  __v: { type: Number, select: false },
  name: { type: String, required: true },
  avatar_url: { type: String },
  introduction: { type: String, select: false }
})

module.exports = model('Topic', topicSchema)
// controllers/topics.js
const Topic = require('../models/topics')

class TopicsCtl {
  async find (ctx) {
    ctx.body = await Topic.find()
  }
  async findById (ctx) {
    const { fields = '' } = ctx.query
    const selectFields = fields.split(';').filter(f => f).map(f => '+' + f).join('')
    const topic = await Topic.findById(ctx.params.id).select(selectFields)
    ctx.body = topic
  }
  async create (ctx) {
    ctx.verifyParams({
      name: { type: 'string', required: true },
      avatar_url: { type: 'string', required: false },
      introduction: { type: 'string', required: false }
    })
    const topic = await new Topic(ctx.request.body).save()
    ctx.body = topic
  }
  async update (ctx) {
    ctx.verifyParams({
      name: { type: 'string', required: false },
      avatar_url: { type: 'string', required: false },
      introduction: { type: 'string', required: false }
    })
    const topic = await Topic.findByIdAndUpdate(ctx.params.id, ctx.request.body)
    ctx.body = topic
  }
}

module.exports = new TopicsCtl()
// routes/topics.js
const jwt = require('koa-jwt')
const Router = require('koa-router')
const router = new Router({ prefix: '/topics' })
const { find, findById, create, update } = require('../controllers/topics')
const { secret } = require('../config')

const auth = jwt({ secret })

router.get('/', find)
router.post('/', auth, create)
router.get('/:id', findById)
router.patch('/:id', auth, update)

module.exports = router

RESTful API 最佳实践——分页

操作步骤

const page = Math.max((ctx.query.page || 1) * 1, 1) - 1
const perPage = Math.max((ctx.query.per_page || 1) * 1, 1)
ctx.body = await User.find().limit(perPage).skip(page * perPage)

RESTful API 最佳实践——模糊搜索

操作步骤

// 在find查询条件中加上正则表达式
ctx.body = await Topic.find({ name: new RegExp(ctx.query.q) })

12-6 用户属性中的话题引用

操作步骤

  1. 将locations、business、employments.company、employments.job、educations.school、educations.major等字段设置为Schema.Types.ObjectID类型绑定为Topic
  async findById (ctx) {
    const { fields = '' } = ctx.query
+    const populateStr = fields.split(';').filter(f => f).map(f => {
+      if (f === 'employments') {
+        return 'employments.company employments.job'
+      }
+      if (f === 'educations') {
+        return 'educations.school educations.major'
+      }
+      return f
+    }).join(' ');
    const user = await User.findById(ctx.params.id).select('+' + fields.split(';').filter(f => f).join('+'))
+      .populate(populateStr);
    if (!user) {
      ctx.throw(404, '用户不存在!')
    }
    ctx.body = user
  }

12-7 RESTful 风格的关注话题接口

操作步骤

  async listFollowingTopics (ctx) {
    const user = await User.findById(ctx.params.id).select('+followingTopics').populate('followingTopics')
    if (!user) {
      ctx.throw(404, '用户不存在')
    }
    ctx.body = user.followingTopics
  }
  async followTopic (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+followingTopics')
    if (!me.followingTopics.map(id => id.toString()).includes(ctx.params.id)) {
      me.followingTopics.push(ctx.params.id)
      me.save()
    }
    ctx.status = 204
  }
  async unfollowTopic (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+followingTopics')
    const index = me.followingTopics.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.followingTopics.splice(index, 1)
      me.save()
    }
    ctx.status = 204
  }

第13章 项目实战之问题模块——复杂的数据库设计

问题模块需求分析

浏览知乎的问题模块功能

image.png

问题模块功能点

用户-问题一对多关系设计与实现

操作步骤

// controller/questions.js
const Question = require('../models/questions')

class QuestionsCtl {
  async find (ctx) {
    const page = Math.max((ctx.query.page || 1) * 1, 1) - 1
    const perPage = Math.max((ctx.query.per_page || 2) * 1, 1)
    const q = new RegExp(ctx.query.q)
    ctx.body = await Question.find({ $or: [{ title: q }, { description: q }] }).limit(perPage).skip(page * perPage)
  }
  async checkQuestionExist (ctx, next) {
    const question = await Question.findById(ctx.params.id).select('+questioner')
    if (!question) {
      ctx.throw(404, '问题不存在!')
    }
    ctx.state.question = question
    await next()
  }
  async findById (ctx) {
    const { fields = '' } = ctx.query
    const selectFields = fields.split(';').filter(f => f).map(f => '+' + f).join('')
    const question = await Question.findById(ctx.params.id).select(selectFields).populate('questioner')
    ctx.body = question
  }
  async create (ctx) {
    ctx.verifyParams({
      title: { type: 'string', required: true },
      description: { type: 'string', required: false },
      introduction: { type: 'string', required: false }
    })
    const question = await new Question({ ...ctx.request.body, questioner: ctx.state.user._id }).save()
    ctx.body = question
  }
  async update (ctx) {
    ctx.verifyParams({
      title: { type: 'string', required: false },
      description: { type: 'string', required: false }
    })
    await ctx.state.question.update(ctx.request.body)
    ctx.body = ctx.state.question
  }
  async checkQuestioner (ctx, next) {
    const { question } = ctx.state
    if (question.questioner.toString() !== ctx.state.user._id) { ctx.throw(403, '没有权限') }
    await next()
  }
  async delete (ctx) {
    await Question.findByIdAndRemove(ctx.params.id)
    ctx.status = 204
  }
}

module.exports = new QuestionsCtl()

// model/questions.js
const mongoose = require('mongoose')

const { Schema, model } = mongoose;

const questionSchema = new Schema({
  __v: { type: Number, select: false },
  title: { type: String, required: true },
  description: { type: String },
  questioner: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false }
})

module.exports = model('Question', questionSchema)

// router/question.js
const jwt = require('koa-jwt')
const Router = require('koa-router')
const router = new Router({ prefix: '/questions' })
const { find, findById, create, update, delete: del, checkQuestionExist, checkQuestioner } = require('../controllers/questions')
const { secret } = require('../config')

const auth = jwt({ secret })

router.get('/', find)
router.post('/', auth, create)
router.get('/:id', checkQuestionExist, findById)
router.patch('/:id', auth, checkQuestionExist, checkQuestioner, update)
router.delete('/:id', auth, checkQuestionExist, checkQuestioner, del)

module.exports = router
// router/users.js 中新增一个列出问题列表的接口

13-3 话题-问题多对多关系设计与实现

操作步骤

// model
  topics: {
    type: [{ type: Schema.Types.ObjectId, ref: 'Topic' }],
    select: false
  }
// controller
  async listQuestions (ctx) {
    const questions = await Question.find({ topics: ctx.params.id })
    ctx.body = questions
  }
// router
router.get('/:id/questions', checkTopicExist, listQuestions)

答案模块需求分析

浏览知乎的答案模块功能

知乎答案界面截图

问题-答案模块二级嵌套的增删改查接口

操作步骤

// model
const mongoose = require('mongoose')

const { Schema, model } = mongoose;

const answerSchema = new Schema({
  __v: { type: Number, select: false },
  content: { type: String, required: true },
  answerer: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
  questionId: { type: String, required: true }
})

module.exports = model('Answer', answerSchema)

// controller
const Answer = require('../models/answers')

class AnswersCtl {
  async find (ctx) {
    const page = Math.max((ctx.query.page || 1) * 1, 1) - 1
    const perPage = Math.max((ctx.query.per_page || 2) * 1, 1)
    const q = new RegExp(ctx.query.q)
    ctx.body = await Answer.find({ content: q, questionId: ctx.params.questionId }).limit(perPage).skip(page * perPage)
  }
  async checkAnswerExist (ctx, next) {
    const answer = await Answer.findById(ctx.params.id).select('+answerer')
    if (!answer) {
      ctx.throw(404, '答案不存在!')
    }
    if (answer.questionId !== ctx.params.questionId) {
      ctx.throw(404, '该问题下没有此答案')
    }
    ctx.state.answer = answer
    await next()
  }
  async findById (ctx) {
    const { fields = '' } = ctx.query
    const selectFields = fields.split(';').filter(f => f).map(f => '+' + f).join('')
    const answer = await Answer.findById(ctx.params.id).select(selectFields).populate('answerer')
    ctx.body = answer
  }
  async create (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: true }
    })
    const answerer = ctx.state.user._id
    const questionId = ctx.params.questionId
    const answer = await new Answer({ ...ctx.request.body, answerer, questionId }).save()
    ctx.body = answer
  }
  async checkAnswerer (ctx, next) {
    const { answer } = ctx.state
    if (answer.answerer.toString() !== ctx.state.user._id) { ctx.throw(403, '没有权限') }
    await next()
  }
  async update (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: false }
    })
    await ctx.state.answer.update(ctx.request.body)
    ctx.body = ctx.state.answer
  }
  async delete (ctx) {
    await Answer.findByIdAndRemove(ctx.params.id)
    ctx.status = 204
  }
}

module.exports = new AnswersCtl()

// router
const jwt = require('koa-jwt')
const Router = require('koa-router')
const router = new Router({ prefix: '/questions/:questionId/answers' })
const { find, findById, create, update, delete: del, checkAnswerExist, checkAnswerer } = require('../controllers/answers')
const { secret } = require('../config')

const auth = jwt({ secret })

router.get('/', find)
router.post('/', auth, create)
router.get('/:id', checkAnswerExist, findById)
router.patch('/:id', auth, checkAnswerExist, checkAnswerer, update)
router.delete('/:id', auth, checkAnswerExist, checkAnswerer, del)

module.exports = router

14-3 互斥关系的赞/踩答案接口设计与实现

操作步骤

// users model
  likingAnswers: {
    type: [{ type: Schema.Types.ObjectId, ref: 'Answer' }],
    select: false
  },
  dislikingAnswers: {
    type: [{ type: Schema.Types.ObjectId, ref: 'Answer' }],
    select: false
  }

// answers model 增加字段
voteCount: { type: Number, required: true, default: 0 }

// 修改 checkAnswerExist 
  async checkAnswerExist (ctx, next) {
    const answer = await Answer.findById(ctx.params.id).select('+answerer')
    if (!answer) {
      ctx.throw(404, '答案不存在!')
    }
    // 只有在删改查答案时才检查此逻辑,赞、踩答案时不检查
    if (ctx.params.questionId && answer.questionId !== ctx.params.questionId) {
      ctx.throw(404, '该问题下没有此答案')
    }
    ctx.state.answer = answer
    await next()
  }

// users controller
  async listLikingAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+likingAnswers').populate('likingAnswers')
    if (!user) {
      ctx.throw(404, '用户不存在')
    }
    ctx.body = user.likingAnswers
  }
  async likeAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+likingAnswers')
    if (!me.likingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
      me.likingAnswers.push(ctx.params.id)
      me.save()
      await Answer.findByIdAndUpdate(ctx.params.id, { $inc: { voteCount: 1 }})
    }
    ctx.status = 204
    await next()
  }
  async unlikeAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+likingAnswers')
    const index = me.likingAnswers.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.likingAnswers.splice(index, 1)
      me.save()
      await Answer.findByIdAndUpdate(ctx.params.id, { $inc: { voteCount: -1 }})
    }
    ctx.status = 204
  }
  async listDislikingAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+dislikingAnswers').populate('dislikingAnswers')
    if (!user) {
      ctx.throw(404, '用户不存在')
    }
    ctx.body = user.dislikingAnswers
  }
  async dislikeAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+dislikingAnswers')
    if (!me.dislikingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
      me.dislikingAnswers.push(ctx.params.id)
      me.save()
    }
    ctx.status = 204
    await next()
  }
  async undislikeAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+dislikingAnswers')
    const index = me.dislikingAnswers.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.dislikingAnswers.splice(index, 1)
      me.save()
    }
    ctx.status = 204
  }

// users router
router.get('/:id/likingAnswers', listLikingAnswers)
router.put('/likingAnswers/:id', auth, checkAnswerExist, likeAnswer, undislikeAnswer)
router.delete('/likingAnswers/:id', auth, checkAnswerExist, unlikeAnswer)
router.get('/:id/dislikingAnswers', listDislikingAnswers)
router.put('/dislikingAnswers/:id', auth, checkAnswerExist, dislikeAnswer, unlikeAnswer)
router.delete('/dislikingAnswers/:id', auth, checkAnswerExist, undislikeAnswer)

14-4 RESTful 风格的收藏答案接口

操作步骤

// user Schema 新增一个字段
  collectingAnswers: {
    type: [{ type: Schema.Types.ObjectId, ref: 'Answer' }],
    select: false
  }
// user controller
  async listCollectingAnswers (ctx) {
    const user = await User.findById(ctx.params.id).select('+collectingAnswers').populate('collectingAnswers')
    if (!user) {
      ctx.throw(404, '用户不存在')
    }
    ctx.body = user.collectingAnswers
  }
  async collectingAnswer (ctx, next) {
    const me = await User.findById(ctx.state.user._id).select('+collectingAnswers')
    if (!me.collectingAnswers.map(id => id.toString()).includes(ctx.params.id)) {
      me.collectingAnswers.push(ctx.params.id)
      me.save()
    }
    ctx.status = 204
    await next()
  }
  async uncollectingAnswer (ctx) {
    const me = await User.findById(ctx.state.user._id).select('+collectingAnswers')
    const index = me.collectingAnswers.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.collectingAnswers.splice(index, 1)
      me.save()
    }
    ctx.status = 204
  }
// user routes
router.get('/:id/collectingAnswers', listCollectingAnswers)
router.put('/collectingAnswers/:id', auth, checkAnswerExist, collectingAnswer)
router.delete('/collectingAnswers/:id', auth, checkAnswerExist, uncollectingAnswer)

第15章 评论模块

15-1 评论模块需求分析

浏览知乎的评论模块功能

知乎评论界面截图

评论模块功能点

15-2 问题-答案-评论模块三级嵌套的增删改查接口

// comment model
const mongoose = require('mongoose')

const { Schema, model } = mongoose;

const commentSchema = new Schema({
  __v: { type: Number, select: false },
  content: { type: String, required: true },
  commentator: { type: Schema.Types.ObjectId, ref: 'User', required: true, select: false },
  questionId: { type: String, required: true },
  answerId: { type: String, required: true }
})

module.exports = model('Comment', commentSchema)

// comment controller
const Comment = require('../models/comments')

class CommentsCtl {
  async find (ctx) {
    const page = Math.max((ctx.query.page || 1) * 1, 1) - 1
    const perPage = Math.max((ctx.query.per_page || 2) * 1, 1)
    const q = new RegExp(ctx.query.q)
    const { questionId, answerId } = ctx.params;
    ctx.body = await Comment.find({ content: q, questionId, answerId }).limit(perPage).skip(page * perPage).populate('commentator')
  }
  async checkCommentExist (ctx, next) {
    const comment = await Comment.findById(ctx.params.id).select('+commentator')
    if (!comment) {
      ctx.throw(404, '评论不存在!')
    }
    // 只有在删改查答案时才检查此逻辑,赞、踩答案时不检查
    if (ctx.params.questionId && comment.questionId !== ctx.params.questionId) {
      ctx.throw(404, '该问题下没有此评论')
    }
    if (ctx.params.answerId && comment.answerId !== ctx.params.answerId) {
      ctx.throw(404, '该答案下没有此评论')
    }
    ctx.state.comment = comment
    await next()
  }
  async findById (ctx) {
    const { fields = '' } = ctx.query
    const selectFields = fields.split(';').filter(f => f).map(f => '+' + f).join('')
    const comment = await Comment.findById(ctx.params.id).select(selectFields).populate('commentator')
    ctx.body = comment
  }
  async create (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: true }
    })
    const commentator = ctx.state.user._id
    const { questionId, answerId } = ctx.params
    const comment = await new Comment({ ...ctx.request.body, commentator, questionId, answerId }).save()
    ctx.body = comment
  }
  async checkCommentator (ctx, next) {
    const { comment } = ctx.state
    if (comment.commentator.toString() !== ctx.state.user._id) { ctx.throw(403, '没有权限') }
    await next()
  }
  async update (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: false }
    })
    await ctx.state.comment.update(ctx.request.body)
    ctx.body = ctx.state.comment
  }
  async delete (ctx) {
    await Comment.findByIdAndRemove(ctx.params.id)
    ctx.status = 204
  }
}

module.exports = new CommentsCtl()

// comment routes
const jwt = require('koa-jwt')
const Router = require('koa-router')
const router = new Router({ prefix: '/questions/:questionId/answers/:answerId/comments' })
const { find, findById, create, update, delete: del, checkCommentExist, checkCommentator } = require('../controllers/comments')
const { secret } = require('../config')

const auth = jwt({ secret })

router.get('/', find)
router.post('/', auth, create)
router.get('/:id', checkCommentExist, findById)
router.patch('/:id', auth, checkCommentExist, checkCommentator, update)
router.delete('/:id', auth, checkCommentExist, checkCommentator, del)

module.exports = router

15-3 一级评论与二级评论接口的设计与实现

操作步骤

// 修改comment.find
async find (ctx) {
    const page = Math.max((ctx.query.page || 1) * 1, 1) - 1
    const perPage = Math.max((ctx.query.per_page || 2) * 1, 1)
    const q = new RegExp(ctx.query.q)
    const { questionId, answerId } = ctx.params;
    const { rootCommentId } = ctx.query
    ctx.body = await Comment.find({ content: q, questionId, answerId, rootCommentId }).limit(perPage).skip(page * perPage).populate('commentator replyTo')
  }
// 修改 comment.create
async create (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: true },
      rootCommentId: { type: 'string', required: false },
      replyTo: { type: 'string', required: false },
    })
    const commentator = ctx.state.user._id
    const { questionId, answerId } = ctx.params
    const comment = await new Comment({ ...ctx.request.body, commentator, questionId, answerId }).save()
    ctx.body = comment
  }
// 修改 comment.update
  async update (ctx) {
    ctx.verifyParams({
      content: { type: 'string', required: false }
    })
    // 只允许更新content属性
    const { content } = ctx.request.body
    await ctx.state.comment.update({ content })
    ctx.body = ctx.state.comment
  }
// Comment Schema增加两个字段:
{
  rootCommentId: { type: String },
  replyTo: { type: Schema.Types.ObjectId, ref: 'User'}
}

15-4 添加日期

操作步骤

Schema的第二个参数:

{ timestamps: true }

第16章 丑媳妇终要见公婆:项目上线、部署与配置

16-1 在服务器上安装Git与Node.js

操作步骤

curl -SL https://deb.nodesource.com/setup_11.x | sudo -E bash -
sudo apt-get install -y nodejs

npm i
npm run dev

16-2 用NGINX实现端口转发

操作步骤

server {
  listen 80;
  server_name: localhost;
  location / {
    proxy_pass http://127.0.0.1:3000
  }
}

配置完后,重启NGINX:
service nginx restart
或:
service nginx reload

16-3 使用 PM2 管理进程

操作步骤

第17章 回顾与总结

回顾课程

重点难点

经验心得

拓展建议

上一篇 下一篇

猜你喜欢

热点阅读