Node 第 8 天
1.  学习目标
◆ 完成获取文章分类列表的功能
◆ 完成新增文章分类的功能
◆ 完成根据 ID 删除文章分类的功能
◆ 完成根据 ID 获取文章分类的功能
◆ 完成根据 ID 更新文章分类的功能
◆ 完成发布新文章的功能
2. 新建 ev_article_cate 数据表
2.1 创建表结构

2.2 新增两条初始化的数据

3. 获取文章分类列表
3.1 实现步骤
- 初始化路由模块
 
- 初始化路由处理函数模块
 
- 获取文章分类列表数据
 
3.2 初始化路由模块
- 创建  
/router/artcate.js 路由模块,并初始化如下的代码结构 
 const express = require('express')
 
  const router = express.Router()
 
  router.get('/cates', (req, res) => {   res.send('ok') })
 
  module.exports = router
 
  | 
 
- 在 
app.js 中导入并使用文章分类的路由模块 
 const artCateRouter = require('./router/userinfo')
 
  app.use('/my/article', artCateRouter)
 
  | 
 
3.3 初始化路由处理函数模块
- 创建 
/router_handler/artcate.js 路由处理函数模块,并初始化如下的代码结构 
 exports.getArticleCates = (req, res) => {   res.send('ok') }
 
  | 
 
- 修改 
/router/artcate.js 中的代码 
 const express = require('express')
 
  const router = express.Router()
  const artcate_handler = require('../router_handler/artcate')
 
  router.get('/cates', artcate_handler.getArticleCates)
 
  module.exports = router
 
  | 
 
3.4 实现获取文章分类列表数据的功能
- 在 
/router_handler/artcate.js 头部导入数据库操作模块 
 const db = require('../db/index')
 
  | 
 
- 定义 
SQL 语句 
  const sql = 'select * from ev_article_cate where is_delete=0 order by id asc'
 
  | 
 
- 调用 
db.query() 执行 SQL 语句 
 db.query(sql, (err, results) => {      if (err) return res.cc(err)
       res.send({     status: 0,     message: '获取文章分类列表成功!',     data: results,   }) })
 
  | 
 
- 完整代码
 
 
  const db = require('../db/index')
 
  exports.getArticleCates = (req, res) => {         const sql = 'select * from ev_article_cate where is_delete=0 order by id asc'
       db.query(sql, (err, results) => {          if (err) return res.cc(err)
           res.send({       status: 0,       message: '获取文章分类列表成功!',       data: results,     })   }) }
 
  | 
 
- 测试
 

4. 新增文章分类
4.1 实现步骤
- 定义路由和处理函数
 
- 验证表单数据
 
- 查询 
分类名称 与 分类别名 是否被占用 
- 实现新增文章分类的功能
 
4.2 定义路由和处理函数
 在 /router/artcate.js 模块中,添加 新增文章分类 的路由  
 router.post('/addcates', artcate_handler.addArticleCates)
 
  | 
 
 在 /router_handler/artcate.js 模块中,定义并向外共享 新增文章分类 的路由处理函数 
 
 exports.addArticleCates = (req, res) => {   res.send('ok') }
 
  | 
 
4.3 验证表单数据
- 创建 
/schema/artcate.js 文章分类数据验证模块 
 const joi = require('@hapi/joi')
 
  const name = joi.string().required() const alias = joi.string().alphanum().required()
 
  exports.add_cate_schema = {   body: {     name,     alias   } }
 
  | 
 
- 在 
/router/artcate.js 模块中,使用 add_cate_schema 对数据进行验证 
 const expressJoi = require('@escook/express-joi')
 
  const { add_cate_schema } = require('../schema/artcate')
 
  router.post('/addcates', expressJoi(add_cate_schema), artcate_handler.addArticleCates)
 
  | 
 
- 验证
 

4.4 分析分类名和别名被占用的四种情况
- 分类名称和分类别名同时被占用
 

- 分类名称和分类别名同时被占用
 

- 只有分类名称被占用
 

- 只有分类别名被占用
 

4.5 写代码判断分类名称和别名是否被占用
- 定义查重的 
SQL 语句 
 const sql = `select * from ev_article_cate where name=? or alias=?`
 
  | 
 
 调用 db.query() 执行查重的操作  
 db.query(sql, [req.body.name, req.body.alias], (err, results) => {      if (err) return res.cc(err)
       if (results.length === 2) return res.cc('分类名称与别名被占用,请更换后重试!')   if (results.length === 1 && results[0].name === req.body.name && results[0].alias === req.body.alias) return res.cc('分类名称与别名被占用,请更换后重试!')      if (results.length === 1 && results[0].name === req.body.name) return res.cc('分类名称被占用,请更换后重试!')   if (results.length === 1 && results[0].alias === req.body.alias) return res.cc('分类别名被占用,请更换后重试!')
     })
 
  | 
 
 完整代码 
 
 exports.addArticleCates = (req, res) => {      const sql = `select * from ev_article_cate where name=? or alias=?`
       db.query(sql, [req.body.name, req.body.alias], (err, results) => {          if (err) return res.cc(err)
           if (results.length === 2) return res.cc('分类名称与别名被占用,请更换后重试!')          if (results.length === 1 && results[0].name === req.body.name && results[0].alias === req.body.alias) return res.cc('分类名称与别名被占用,请更换后重试!')     if (results.length === 1 && results[0].name === req.body.name) return res.cc('分类名称被占用,请更换后重试!')     if (results.length === 1 && results[0].alias === req.body.alias) return res.cc('分类别名被占用,请更换后重试!')
      res.send('Ok')   }) }
 
  | 
 
4.6 实现新增文章分类的功能
- 定义新增文章分类的 
SQL 语句 
 const sql = `insert into ev_article_cate set ?`
 
  | 
 
- 调用 
db.query() 执行新增文章分类的 SQL 语句 
 db.query(sql, req.body, (err, results) => {      if (err) return res.cc(err)
       if (results.affectedRows !== 1) return res.cc('新增文章分类失败!')
       res.cc('新增文章分类成功!', 0) })
 
  | 
 
- 完整代码
 
 exports.addArticleCates = (req, res) => {      const sql = `select * from ev_article_cate where name=? or alias=?`
       db.query(sql, [req.body.name, req.body.alias], (err, results) => {          if (err) return res.cc(err)
           if (results.length === 2) return res.cc('分类名称与别名被占用,请更换后重试!')          if (results.length === 1 && results[0].name === req.body.name && results[0].alias === req.body.alias) return res.cc('分类名称与别名被占用,请更换后重试!')     if (results.length === 1 && results[0].name === req.body.name) return res.cc('分类名称被占用,请更换后重试!')     if (results.length === 1 && results[0].alias === req.body.alias) return res.cc('分类别名被占用,请更换后重试!')
           const sql = `insert into ev_article_cate set ?`
           db.query(sql, req.body, (err, results) => {              if (err) return res.cc(err)
               if (results.affectedRows !== 1) return res.cc('新增文章分类失败!')
               res.cc('新增文章分类成功!', 0)     })   }) }
 
  | 
 
- 测试
 

5. 根据 id 删除文章分类
5.1 实现步骤
- 定义路由和处理函数
 
- 验证表单数据
 
- 实现删除文章分类的功能
 
5.2 定义路由和处理函数
- 在 
/router/artcate.js 模块中,添加 删除文章分类 的路由 
 router.get('/deletecate/:id', artcate_handler.deleteCateById)
 
  | 
 
- 在 
/router_handler/artcate.js 模块中,定义并向外共享 删除文章分类 的路由处理函数 
 exports.deleteCateById = (req, res) => {   res.send('ok') }
 
  | 
 
- 验证
 

5.3 验证客户端提交到服务器的 id 值
- 在 
/schema/artcate.js 验证规则模块中,定义 id 的验证规则 
 const id = joi.number().integer().min(1).required()
 
  | 
 
- 并使用 
exports 向外共享如下的 验证规则对象 
 exports.delete_cate_schema = {   params: {     id   } }
 
  | 
 
 在 /router/artcate.js 模块中,导入需要的验证规则对象,并在路由中使用  
 const { delete_cate_schema } = require('../schema/artcate')
 
  router.get('/deletecate/:id', expressJoi(delete_cate_schema), artcate_handler.deleteCateById)
 
  | 
 
 验证 
 

5.4 实现删除文章分类的功能
- 定义删除文章分类的 
SQL 语句 
 const sql = `update ev_article_cate set is_delete=1 where id=?`
 
  | 
 
- 调用 
db.query() 执行删除文章分类的 SQL 语句 
 db.query(sql, req.params.id, (err, results) => {      if (err) return res.cc(err)
       if (results.affectedRows !== 1) return res.cc('删除文章分类失败!')
       res.cc('删除文章分类成功!', 0) })
 
  | 
 
- 完整代码
 
 exports.deleteCateById = (req, res) => {      const sql = `update ev_article_cate set is_delete=1 where id=?`
       db.query(sql, req.params.id, (err, results) => {          if (err) return res.cc(err)             if (results.affectedRows !== 1) return res.cc('删除文章分类失败!')             res.cc('删除文章分类成功!', 0)   }) }
 
  | 
 
- 验证
 

6. 根据 id 获取文章分类
6.1 实现步骤
- 定义路由和处理函数
 
- 验证表单数据
 
- 实现获取文章分类的功能
 
6.2 定义路由和处理函数
- 在 
/router/artcate.js 模块中,添加 根据 Id 获取文章分类 的路由 
 router.get('/cates/:id', artcate_handler.getArticleById)
 
  | 
 
- 在 
/router_handler/artcate.js 模块中,定义并向外共享 根据 Id 获取文章分类 的路由处理函数 
 exports.getArticleById = (req, res) => {   res.send('ok') }
 
  | 
 
- 验证
 

6.3 验证客户端提交到服务器的 id 值
- 在 
/schema/artcate.js 验证规则模块中,使用 exports 向外共享如下的 验证规则对象 
 exports.get_cate_schema = {   params: {     id   } }
 
  | 
 
- 在 
/router/artcate.js 模块中,导入需要的验证规则对象,并在路由中使用 
 const { get_cate_schema } = require('../schema/artcate')
 
  router.get('/cates/:id', expressJoi(get_cate_schema), artcate_handler.getArticleById)
 
  | 
 
- 验证
 

6.4 实现获取文章分类的功能
- 定义根据 Id 获取文章分类的 
SQL 语句 
 const sql = `select * from ev_article_cate where id=?`
 
  | 
 
- 调用 
db.query() 执行 SQL 语句 
 db.query(sql, req.params.id, (err, results) => {      if (err) return res.cc(err)
       if (results.length !== 1) return res.cc('获取文章分类数据失败!')
       res.send({     status: 0,     message: '获取文章分类数据成功!',     data: results[0],   }) })
 
  | 
 
- 完整代码
 
 exports.getArticleById = (req, res) => {      const sql = `select * from ev_article_cate where id=?`
       db.query(sql, req.params.id, (err, results) => {          if (err) return res.cc(err)             if (results.length !== 1) return res.cc('获取文章分类数据失败!')             res.send({       status: 0,       message: '获取文章分类数据成功!',       data: results[0],     })   }) }
 
  | 
 
- 验证
 

7. 根据 id 更新文章分类
7.1 实现步骤
- 定义路由和处理函数
 
- 验证表单数据
 
- 查询 
分类名称 与 分类别名 是否被占用 
- 实现更新文章分类的功能
 
7.2 定义路由和处理函数
- 在 
/router/artcate.js 模块中,添加 更新文章分类 的路由 
 router.post('/updatecate', artcate_handler.updateCateById)
 
  | 
 
- 在 
/router_handler/artcate.js 模块中,定义并向外共享 更新文章分类 的路由处理函数 
 exports.updateCateById = (req, res) => {   res.send('ok') }
 
  | 
 
- 验证
 

7.3 验证表单数据
- 在 
/schema/artcate.js 验证规则模块中,使用 exports 向外共享如下的 验证规则对象 
 exports.update_cate_schema = {   body: {     Id: id,     name,     alias   } }
 
  | 
 
- 在 
/router/artcate.js 模块中,导入需要的验证规则对象,并在路由中使用 
 const { update_cate_schema } = require('../schema/artcate')
 
  router.post('/updatecate', expressJoi(update_cate_schema), artcate_handler.updateCateById)
 
  | 
 
- 验证
 

7.4 查询分类名称与别名是否被占用
- 定义查重的 
SQL 语句 
 const sql = `select * from ev_article_cate where Id<>? and (name=? or alias=?)`
 
  | 
 
- 调用 
db.query() 执行查重的操作 
 db.query(sql, [req.body.Id, req.body.name, req.body.alias], (err, results) => {      if (err) return res.cc(err)
       if (results.length === 2) return res.cc('分类名称与别名被占用,请更换后重试!')   if (results.length === 1 && results[0].name === req.body.name && results[0].alias === req.body.alias) return res.cc('分类名称与别名被占用,请更换后重试!')      if (results.length === 1 && results[0].name === req.body.name) return res.cc('分类名称被占用,请更换后重试!')   if (results.length === 1 && results[0].alias === req.body.alias) return res.cc('分类别名被占用,请更换后重试!')
    res.send('ok') })
 
  | 
 
- 验证
 

7.5 实现更新文章分类的功能
- 定义更新文章分类的 
SQL 语句 
 const sql = `update ev_article_cate set ? where Id=?`
 
  | 
 
- 调用 
db.query() 执行 SQL 语句 
 db.query(sql, [req.body, req.body.Id], (err, results) => {      if (err) return res.cc(err)
       if (results.affectedRows !== 1) return res.cc('更新文章分类失败!')
       res.cc('更新文章分类成功!', 0) })
 
  | 
 
- 完整代码
 
 exports.updateCateById = (req, res) => {      const sql = `select * from ev_article_cate where Id<>? and (name=? or alias=?)`
       db.query(sql, [req.body.Id, req.body.name, req.body.alias], (err, results) => {          if (err) return res.cc(err)
           if (results.length === 2) return res.cc('分类名称与别名被占用,请更换后重试!')     if (results.length === 1 && results[0].name === req.body.name && results[0].alias === req.body.alias) return res.cc('分类名称与别名被占用,请更换后重试!')          if (results.length === 1 && results[0].name === req.body.name) return res.cc('分类名称被占用,请更换后重试!')     if (results.length === 1 && results[0].alias === req.body.alias) return res.cc('分类别名被占用,请更换后重试!')
 
           const sql = `update ev_article_cate set ? where Id=?`
           db.query(sql, [req.body, req.body.Id], (err, results) => {              if (err) return res.cc(err)
               if (results.affectedRows !== 1) return res.cc('更新文章分类失败!')
               res.cc('更新文章分类成功!', 0)     })   }) }
 
  | 
 
- 验证
 

8. 发布新文章
8.1 新建 ev_articles 表

8.2 实现步骤
- 初始化路由模块
 
- 初始化路由处理函数模块
 
- 使用 
multer 解析表单数据 
- 验证表单数据
 
- 实现发布文章的功能
 
8.3 初始化路由模块
- 创建 
/router/article.js 路由模块,并初始化如下的代码结构 
 const express = require('express')
  const router = express.Router()
 
  router.post('/add', (req, res) => {   res.send('ok') })
 
  module.exports = router
 
  | 
 
- 在 
app.js 中导入并使用文章的路由模块 
 const articleRouter = require('./router/article')
 
  app.use('/my/article', articleRouter)
 
  | 
 
8.4 初始化路由处理函数模块
- 创建 
/router_handler/article.js 路由处理函数模块,并初始化代码结构 
 exports.addArticle = (req, res) => {   res.send('ok') }
 
  | 
 
 修改 /router/article.js 中的代码  
 const article_handler = require('../router_handler/article')
 
  router.post('/add', article_handler.addArticle)
 
  | 
 
 完整代码 
 
 const express = require('express')
  const router = express.Router()
 
  const article_handler = require('../router_handler/article')
 
  router.post('/add', article_handler.addArticle)
 
  module.exports = router
 
  | 
 
-  验证 
  
8.5 了解 multer 的作用
- 使用 
express.urlencoded() 中间件无法解析 multipart/form-data 格式的请求体数据,也就是说无法接收到 fomData 表单传递过来的数据 
- 推荐使用 
multer 来解析 multipart/form-data 格式的表单数据 
8.6 安装和配置 multer
 运行如下的终端命令,在项目中安装 multer  
 
 在 /router/article.js 模块中导入并配置 multer 
 
 const multer = require('multer')
  const path = require('path')
 
  const upload = multer({ dest: path.join(__dirname, '../uploads') })
 
  | 
 
- 修改 
发布新文章 的路由 
- 使用 
upload.single() 方法,它是一个局部生效的中间件,用来解析 FormData 格式的表单数据 
- 将文件类型的数据,解析并挂载到 
req.file 属性中 
- 将文本类型的数据,解析并挂载到 
req.body 属性中 
 router.post('/add', upload.single('cover_img'), article_handler.addArticle)
 
  | 
 
- 在 
/router_handler/article.js 模块的 addArticle 处理函数中,将 multer 解析出来的数据进行打印 
 exports.addArticle = (req, res) => {   console.log(req.body)    console.log('--------分割线----------')   console.log(req.file) 
    res.send('ok') }
 
  | 
 
- 验证
 

8.8 对 req.body 中的数据进行验证
- 实现思路:通过 
express-joi自动验证 req.body 中的文本数据;通过 if 判断手动验证 req.file 中的文件数据 
 const joi = require('@hapi/joi')
 
  const title = joi.string().required() const cate_id = joi.number().integer().min(1).required() const content = joi.string().required().allow('') const state = joi.string().valid('已发布', '草稿').required()
 
  exports.add_article_schema = {   body: {     title,     cate_id,     content,     state   } }
 
  | 
 
-  在 
/router/article.js 模块中,导入需要的验证规则对象,并在路由中使用   const expressJoi = require('@escook/express-joi')
  const { add_article_schema } = require('../schema/article')
 
 
 
 
  router.post('/add', upload.single('cover_img'), expressJoi(add_article_schema), article_handler.addArticle)
 
  | 
 
8.9 对 req.file 中的数据进行验证
-  在 
/router_handler/article.js 模块中的 addArticle 处理函数中,通过 if 判断客户端是否提交了 封面图片   exports.addArticle = (req, res) => {   console.log(req.body)    console.log('--------分割线----------')   console.log(req.file) 
       if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必选参数!')
    res.send('ok') }
 
  | 
 
8.10 处理文章信息对象
- 整理要插入数据库的文章信息对象
 
const articleInfo = {      ...req.body,      cover_img: path.join('/uploads', req.file.filename),      pub_date: new Date(),      author_id: req.user.id, }
  | 
 
- 完整代码
 
 const path = require('path')
 
  exports.addArticle = (req, res) => {   console.log(req.body)    console.log('--------分割线----------')   console.log(req.file) 
       if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必选参数!')
    const articleInfo = {          ...req.body,          cover_img: path.join('/uploads', req.file.filename),          pub_date: new Date(),          author_id: req.user.id,   }
    res.send(articleInfo) }
 
  | 
 
- 验证
 

8.11 实现发布文章的功能
- 定义发布文章的 
SQL 语句 
 const sql = `insert into ev_articles set ?`
 
  | 
 
- 调用 
db.query() 执行发布文章的 SQL 语句 
 const db = require('../db/index')
 
  db.query(sql, articleInfo, (err, results) => {      if (err) return res.cc(err)
       if (results.affectedRows !== 1) return res.cc('发布文章失败!')
       res.cc('发布文章成功', 0) })
 
  | 
 
- 完整代码
 
 const path = require('path')
 
  exports.addArticle = (req, res) => {
       if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必选参数!')
    const articleInfo = {          ...req.body,          cover_img: path.join('/uploads', req.file.filename),          pub_date: new Date(),          author_id: req.user.id,   }
       const sql = `insert into ev_articles set ?`
       const db = require('../db/index')
       db.query(sql, articleInfo, (err, results) => {          if (err) return res.cc(err)
           if (results.affectedRows !== 1) return res.cc('发布文章失败!')
           res.cc('发布文章成功', 0)   }) }
 
  | 
 
- 验证
 

8.12 托管静态资源文件
- 在 
app.js 中,使用 express.static() 中间件,将 uploads 目录中的图片托管为静态资源 
 app.use('/uploads', express.static('./uploads'))
 
  |