Node.js<二十一>——项目实战-评论模块
该模块是基于动态管理模块之后做的,主要实现的功能有:评论动态、回复评论、删除评论、获取评论、获取评论/回复的回复,该文章主要是帮助自己复盘这个模块所写的代码,大家单看的话可能收益不是很大
- router/comment.router.js
既然是一个新的模块,那么肯定是要新建一个路由的,里面注册了很多api
所对应的中间件,比如添加评论、删除评论等等。我们开发api时尽量要符合restful
风格:宾语 URL
应该全部使用名词复数,比如下面的comments
和replies
,如果是针对于评论进行操作的,则我们要让用户以params
的形式拼接到url上传递过来
const Router = require('koa-router')
const {
verifyAuth,
verifyPermission
} = require('../middleware/auth.middleware')
const {
create,
reply,
remove,
update,
list,
replyList
} = require('../controller/comment.controller')
const commentRouter = new Router({ prefix: '/comments' })
// 添加评论
commentRouter.post('/', verifyAuth, create)
// 回复评论
commentRouter.post('/:commentId/reply', verifyAuth, reply)
// 删除评论
commentRouter.delete('/:commentId/delete', verifyAuth, verifyPermission, remove)
// 获取评论
commentRouter.get('/', list)
// 获取评论/回复的回复
commentRouter.get('/:commentId/replies', replyList)
module.exports = commentRouter
- controller/comment.controller.js
这个文件通过类的方式,在其原型上面集成了对应api
的操作函数,各个函数的大体思路很相似,如果是登录之后才能做的操作,就需要先从ctx.body
中获取到从token
中解析出来的userId
,其次需要知道有关操作的信息,比如说添加评论需要知道对应的内容content
和动态id
等等;获取到这些之后,再将参数传递到有关数据库操作的函数中去,完成之后响应结果即可
const commentService = require("../service/comment.service")
class CommentController {
// 添加评论
async create(ctx, next) {
const { id: userId } = ctx.user
const { dynamicId, content } = ctx.request.body
const results = await commentService.create(userId, dynamicId, content)
ctx.body = results
}
// 回复评论
async reply(ctx, next) {
const { id: userId } = ctx.user
const { commentId } = ctx.params
const { dynamicId, content } = ctx.request.body
const results = await commentService.reply(userId, dynamicId, content, commentId)
ctx.body = results
}
// 删除评论
async remove(ctx, next) {
const { commentId } = ctx.params
const results = await commentService.delete(commentId)
ctx.body = results
}
// 获取评论
async list(ctx, next) {
const { dynamicId, offset, limit } = ctx.query
const results = await commentService.getList(dynamicId, offset, limit)
ctx.body = results
}
// 获取评论/回复的回复
async replyList(ctx, next) {
const { commentId } = ctx.params
const { offset, limit } = ctx.query
const results = await commentService.getReplyList(commentId, offset, limit)
ctx.body = results
}
}
module.exports = new CommentController()
- service/comment.service.js
一般直接操作数据库的代码我们会分离出来放到service
文件夹中,这样子更加利于管理,这里并没有提供编辑评论/回复的操作,因为这样可能会造成误解;其次发现有一些操作的sql
语句很大一部分是重复的,我们可以单独抽离成一个变量或者是一个函数,通过参数来决定最终sql语句的返回值,这样可以在一定程度上简化我们的代码
const connection = require('../app/database')
const getSqlFragment = (isReply) => {
return (
`INSERT INTO comment
(user_id, dynamic_id, content${isReply ? ', comment_id' : ''})
VALUES(?, ?, ?${isReply ? ', ?' : ''});`
)
}
const sqlFragment = `
SELECT c.id id, c.content content, c.dynamic_id dynamicId, c.comment_id commentId, c.createAt createTime, c.updateAt updateTime,
JSON_OBJECT('id', u.id, 'name', u.name) user
FROM comment c
LEFT JOIN user u
ON u.id = c.user_id
`
class CommentService {
// 添加评论
async create(userId, dynamicId, content) {
const statement = getSqlFragment()
const [results] = await connection.execute(statement, [userId, dynamicId, content])
return results
}
// 回复评论/回复
async reply(userId, dynamicId, content, commentId) {
const statement = getSqlFragment(true)
const [results] = await connection.execute(statement, [userId, dynamicId, content, commentId])
return results
}
// 删除评论
async delete(commentId) {
const statement = `DELETE FROM comment WHERE id = ?;`
const [results] = await connection.execute(statement, [commentId])
return results
}
// 获取评论
async getList(dynamicId, offset = 0, limit = 5) {
const statement = `
${sqlFragment}
WHERE dynamic_id = ?
LIMIT ?, ?;
`
const [results] = await connection.execute(statement, [dynamicId, offset, limit])
return results
}
// 获取回复
async getReplyList(commentId, offset = 0, limit = 5) {
const statement = `
${sqlFragment}
WHERE comment_id = ?
LIMIT ?, ?;
`
const [results] = await connection.execute(statement, [commentId, offset, limit])
return results
}
}
module.exports = new CommentService()
- middleware/duth.middleware.js
但是评论的删除涉及到权限的认证,比如用户1
不能删除用户2的
评论。我们之前是写过一个针对于动态的权限验证中间件verifyPermission
的,现在我们要对其做一个改动,让其可以适用于所有模块的权限验证
既然要做到适用于每一个权限的操作,我们就要考虑到的它的通用性。仔细想想我们是怎么检验权限的?不就是先看看用户操作的目标存不存在,如果不存在就返回报错信息,如果目标存在,再根据用户id
比较一下目标所对应的用户是不是当前的操作者,如果不是则说明当前操作者没有权限,返回错误信息,如果有权限就进入到下一个中间件中
但是又有一个问题,每一个模块操作的表都不一样,我们有什么办法可以获取到表名称呢?一般需要权限操作的都需要传对应的id
过来,而对应的名称前面一部分就是我们的表名称,比如说commentId
对应的表就是comment
,我们只需要截取一下即可
const errTypes = require('../constants/err-types')
const { checkResource } = require('../service/auth.service')
// middleware/auth.middleware.js
const verifyPermission = async (ctx, next) => {
// 从解析出来的token中获取用户id,用于下面权限的校验
const { id: userId } = ctx.user
// 由于一般需要此权限操作的都会通过params传一个目标id过来,其key的前一部分刚好对应我们的表名
const key = Object.keys(ctx.params)[0]
const id = ctx.params[key]
const tableName = key?.replace('Id', '')
// 我们需要利用checkResource函数从数据库中查询对应数据
const result = await checkResource(id, tableName)
// 通过查询到的数据是否为空来判断用户操作的目标是否存在,不存在则返回错误信息
if (!result) {
const error = new Error(errTypes.TARGET_IS_NOT_EXISTS)
return ctx.app.emit('error', error, ctx)
// 判断当前用户和操作目标对应的用户是不是同一个,如果不是则说明其没有权限,返回错误信息
} else if (result.user_id !== userId) {
const error = new Error(errTypes.UN_PERMISSION)
return ctx.app.emit('error', error, ctx)
}
await next()
}
module.exports = {
verifyPermission
}
- service/auth.service.js
由于以前的checkResource
函数是针对于动态的,但现在需要改造成通用的,所以其内部的查询sql
语句也要根据传入的参数而动态改变
const connection = require('../app/database')
class AuthService {
async checkResource(id, tableName) {
const statement = `SELECT * FROM ${tableName} WHERE id = ?;`
const [results] = await connection.execute(statement, [id])
return results[0]
}
}
module.exports = new AuthService()
至此,我们的评论模块就基本完成了
转载自:https://juejin.cn/post/7107075651483484167