Node.js<二十三>——项目实战-动态标签管理

201 阅读5分钟
  1. router/label.router.js

开发任何一个新的模块都需要创建一个单独的路由文件,标签管理也不例外,我们先为标签模块添加上创建标签接口和获取标签接口,这两个接口对应的核心中间件执行之前都要先验证了用户用户是否处在登录状态

const Router = require('koa-router')
const { verifyAuth } = require('../middleware/auth.middleware')

const {
  list,
  create
} = require('../controller/label.controller')

const labelRouter = new Router({ prefix: '/labels' })

labelRouter.get('/', verifyAuth, list)
labelRouter.post('/', verifyAuth, create)

module.exports = labelRouter
  1. controller/label.controller.js

我们具体来看下上面两个接口对应的中间件吧?获取标签列表接口没有什么独特的,主要就是将从数据库中查询到的列表直接响应给客户端,我们重点说明一下创建标签接口,因为标签是不能重复的,所以在真正添加到数据表之前我们还要有个查询的操作,根据想要新增的标签名查询数据表中有没有该标签,如果标签已经存在,则不需要在数据表中重复添加;如果标签不存在,则需要调用对应的函数去数据表中先添加标签

const service = require('../service/label.service')

class LabelController {
  async list(ctx, next) {
    const results = await service.list()
    ctx.body = results
  }

  async create(ctx, next) {
    const { name } = ctx.request.body
    // 根据标签名称在数据库中查询记录
    const label = await service.getLabelByName(name)
    // 如果标签表中已有对应的标签,则返回错误信息,如果没有则在数据表中添加
    if (!label) {
      const results = await service.create(name)
      ctx.body = results
    } else {
      ctx.body = '标签已经被创建过了'
    }
  }
}

module.exports = new LabelController()
  1. service/label.service.js

我们之前调用有关数据库操作的函数,其实就是被放到service文件夹中的,和其他文件一样,都是通过利用连接池将书写的sql预编译语句转化为具体对数据库的操作

const connection = require('../app/database')

class LabelService {
  // 通过标签名称查询对应数据
  async getLabelByName(name) {
    const statement = `SELECT * FROM label WHERE name = ?;`
    const [results] = await connection.execute(statement, [name])
    return results[0]
  }

  // 查询标签列表
  async list() {
    const statement = `SELECT id, name, createAt createTime, updateAt updateTime FROM label;`
    const [results] = await connection.execute(statement, [])
    return results
  }

  // 创建标签
  async create(name) {
    const statement = `INSERT INTO label (name) VALUES (?);`
    const [results] = await connection.execute(statement, [name])
    return results
  }
}

module.exports = new LabelService()
  1. router/dynamic.router.js

因为标签是和动态相绑定在一起的,所以我们肯定要有为某个动态上传标签的功能。用户发送对应的请求之后,首先肯定需要检查其登录态,然后检查其操作的动态是不是本人发布的,如果不是直接返回错误信息;如果是,还需要先检查一遍其想要上传的标签是不是都在数据表中的,如果有一些动态不在数据表中,还需要先添加上去之后再插入到动态-标签的关系表中去

const Router = require('koa-router')

const {
  verifyAuth,
  verifyPermission
} = require('../middleware/auth.middleware')
const {
  addLabels
} = require('../controller/dynamic.controller')
const { verifyLabelExists } = require('../middleware/label.middleware')

const dynamicRouter = new Router({ prefix: '/dynamics' })

// 为动态添加标签
dynamicRouter.post('/:dynamicId/labels', verifyAuth, verifyPermission, verifyLabelExists, addLabels)

module.exports = dynamicRouter
  1. middleware/label.middleware.js

这里主要讲一下verifyLabelExists这个中间件,其主要作用就是去检查用户为动态添加的标签列表中是否都在我们的标签表中,如果有不在的,则需要帮助他们创建。检查的方法很简单,循环遍历用户上传的标签列表,根据标签名去数据库中查看是否存在,如果不存在,则需要为其创建,如果存在则不作任何操作,最后所有的标签都满足条件之后将一个包装了所有标签id的数组放到ctx.labels属性下,这样下一个中间件就可以通过访问这个对象的方式去获取到用户想要绑定动态的标签id列表了

const service = require("../service/label.service")

const verifyLabelExists = async (ctx, next) => {
  const { labels } = ctx.request.body
  const labelsIdArr = []
  // 循环遍历标签数据,不存在标签表中的需要添加,存在标签表中的不做处理
  for (const labelName of labels) {
    const label = await service.getLabelByName(labelName)
    if (!label) {
      const { insertId } = await service.create(labelName)
      labelsIdArr.push(insertId)
    } else labelsIdArr.push(label.id)
  }
  ctx.labels = labelsIdArr
  await next()
}

module.exports = {
  verifyLabelExists
}
  1. controller/dynamic.controller.js

当用户所有的标签都存在于标签表中之后,我们就可以在动态-标签关系表中将ctx.labels存储的标签idparams中的动态id一起插入到表中了,这样我们才知道了哪个动态对应着哪些标签

const fs = require('fs')

const { PICTURE_PATH } = require('../constants/file-path')
const dynamicService = require('../service/dynamic.service')
const fileService = require('../service/file.service')

class DynamicController {
  async addLabels(ctx, next) {
    const { dynamicId } = ctx.params
    const labelsIdArr = ctx.labels
    for (const labelId of labelsIdArr) {
      // 根据标签id去标签表中查找对应的数据,如果数据不存在则需要将未绑定的信息插入进去
      const isExists = await dynamicService.hasLabel(labelId)
      if (!isExists) {
        await dynamicService.addLabel(dynamicId, labelId)
      }
    }
    ctx.body = '给动态添加标签成功'
  }
}

module.exports = new DynamicController()
  1. service/dynamic.service.js

这里主要想讲的是如何在查找动态的时候顺便将其关联的标签信息返回,我们用到了子查询结合IF语句来防止查询出来的结果中为字段均为null的情况,注意,如果一段sql语句中用到了多个JSON_ARRAYAGG,则sql语句的末尾必须要使用GROUP BY进行分组,最终将对应动态的标签信息整合成了一个字段放置到查询结果中去

const connection = require('../app/database')

const sqlFragment = `
  SELECT d.id id, d.content content, d.createAt createTime, d.updateAt updateTime, 
  (SELECT COUNT(*) FROM comment WHERE dynamic_id = d.id) commentCount,
  JSON_OBJECT('id',u.id,'name', u.name, 'avatarUrl', u.avatar_url) user, 
	(SELECT COUNT(*) FROM dynamics_labels WHERE dynamic_id = d.id) labelCount,
	IF(COUNT(l.id),JSON_ARRAYAGG(
	JSON_OBJECT('id', l.id, 'name', l.name)
	), JSON_ARRAY()) labels,
  (SELECT JSON_ARRAYAGG(CONCAT('http://localhost:3000/dynamics/images/',f.filename)) FROM file f WHERE 
  f.dynamic_id = d.id) images
  FROM dynamic d
  LEFT JOIN user u ON d.user_id = u.id
	LEFT JOIN dynamics_labels dl ON dl.dynamic_id = d.id
	LEFT JOIN label l ON dl.label_id = l.id
  GROUP BY d.id
`

class DynamicService {
  async getDetail(dynamicId) {
    const statement = `
      ${sqlFragment}
      HAVING d.id = ?;
    `
    const [results] = await connection.execute(statement, [dynamicId])
    return results[0]
  }

  async hasLabel(labelId) {
    const statement = `SELECT * FROM dynamics_labels WHERE label_id = ?;`
    const [results] = await connection.execute(statement, [labelId])
    return results[0]
  }

  async addLabel(dynamicId, labelId) {
    const statement = `INSERT INTO dynamics_labels (dynamic_id, label_id) VALUES(?, ?);`
    const [results] = await connection.execute(statement, [dynamicId, labelId])
    return results
  }
}

module.exports = new DynamicService()

到目前为止,我们的动态标签模块已经基本完成,现在可以正常的为动态添加标签,而且在查询动态时对应的数据也附带着标签数目和标签内容