美文网首页
“真实世界”全栈开发-3.8-博文的增查改删

“真实世界”全栈开发-3.8-博文的增查改删

作者: 桥头堡2015 | 来源:发表于2018-02-10 09:44 被阅读26次

    对数据做增查改删(CRUD)是交互式网络应用的核心功能。对于用户,我们只提供了前三项操作;而对博文,四项操作我们的应用都要提供。

    创建博文的模型

    如本系列第二部分所说的,博文应该有如下数据:

    • slug:为后端为每篇博文自动生成的独一字符串,用于数据库里的查询。如果你对这个概念不熟悉,请阅读这一维基百科页面
    • title:标题
    • body:我们的博客平台采用Markdown编辑器,所以body里存储的是本篇博文的Markdown文本
    • description:对本篇博文的一小段介绍
    • favoritesCount:获得的点赞数
    • tagList:标签列表
    • author:作者,为其公共信息对象

    我们先来为上面的数据新建Mongoose模式,并将其注册为可用的模型。新建models/Article.js文件,写入:

    const mongoose = require('mongoose');
    const uniqueValidator = require('mongoose-unique-validator');
    const slug = require('slug');
    
    const ArticleSchema = new mongoose.Schema({
      slug: {type: String, lowercase: true, unique: true},
      title: String,
      description: String,
      body: String,
      favoritesCount: {type: Number, default: 0},
      tagList: [{type: String}],
      author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}
    }, {timestamps: true});
    
    ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});
    
    mongoose.model('Article', ArticleSchema);
    

    如同用户模式里一样,我们这里用到了mongoose-unique-validator来验证slug是唯一的。

    上面的代码已经导入了slug包。接下来,我们要定义一个新方法,使用这个包来生成博文slug。为了保证slug的唯一性,我们会在原标题生成的字符串后再加上六个随机字符。

    ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});
    
    // +++
    ArticleSchema.methods.slugify = function() {
      this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
    };
    // +++
    
    mongoose.model('Article', ArticleSchema);
    

    什么时候调用这个方法呢?每当一篇新博文创建后或者是一篇已有博文的标题发生改变后,往数据库保存的时候,我们需要调用这个方法生成新的slug。

    我们可以借助Mongoose中间件来在上述的场合自动调用slugify方法。

    models/Article.js加入以下代码:

    // +++
    ArticleSchema.pre('validate', function (next) {
      if (!this.slug || this.isModified('title'))
        this.slugify();
      return next();
    });
    // +++
    
    mongoose.model('Article', ArticleSchema);
    

    slug的生成应该发生在Mongoose检验数据之前,否则没有slug字段,检验必然失败。这也是上面代码中pre'validate'表达的意思。

    另外注意,这里回调函数中的this指向的是当前的博文对象;因此,定义Mongoose中间件时也不能用箭头函数。

    最后,我们还需要添加一个方法,返回博文的JSON对象。不要忘了加入Mongoose自动创建的createAtupdateAt这两条数据。

    // +++
    ArticleSchema.methods.toJSONFor = function (user) {
      return {
        slug: this.slug,
        title: this.title,
        description: this.description,
        body: this.body,
        createdAt: this.createdAt,
        updatedAt: this.updatedAt,
        tagList: this.tagList,
        favoritesCount: this.favoritesCount,
        author: this.author.toProfileJSONFor(user)
      };
    };
    // +++
    
    ArticleSchema.pre('validate', ...);
    

    值得指出的是,author一行,我们用到了之前为用户模型定义的toProfileJSONFor(user)方法。

    最后,我们需要在后端应用中运行上面的脚本,否则之后的中间件无法使用Article模型。

    app.js文件里加入一行代码:

    require('./models/User');
    // +++
    require('./models/Article');
    // +++
    require('./config/passport');
    

    到此博文模型就创建完成了。我们接下来要做的就是添加增查改删博文的路由及相应的中间件。

    为博文操作新建路由对象

    跟之前的步骤一样,我们首先要为博文的所有操作(其跟URL都将是/api/articles)新建一个Router对象。

    新建文件/routes/api/articles.js,写入:

    const router = require('express').Router();
    const passport = require('passport');
    const mongoose = require('mongoose');
    const Article = mongoose.model('Article');
    const User = mongoose.model('User');
    const auth = require('../auth');
    
    module.exports = router;
    

    同样地,这个路由需要注册到API的主路由上。打开routes/api/index.js,加入:

    router.use('/profiles', require('./profiles'));
    // +++
    router.use('/articles', require('./articles'));
    // +++
    

    路由对象建好了,也注册到主路由上了,接下来就该实现具体的中间件了。

    新建博文

    新建博文的端点为POST /api/articles,而且只有登录的用户才能执行,所以需要身份验证。

    routes/api/articles.js加入如下代码:

    // +++
    const loadCurrentUser = require('./user').loadCurrentUser;
    
    const respondArticle = (req, res, next) => {
      return res.json({article: res.locals.article.otJSONFor(res.locals.res)});
    }
    
    router.post('/', auth.required, loadCurrentUser, (req, res, next) => {
      const article = new Article(req.body.article);
      article.author = res.locals.user;
      return article.save().then(() => {
        res.locals.article = article;
        return next();
      }).catch(next);
    }, respondArticle);
    // +++
    
    module.exports = router;
    

    从URL中获取博文slug

    博文的查、改、删操作,都需要从数据库中通过其slug读取该博文的数据。如同上一讲获取用户名一样,我们可以在routes/api/articles.js中用router.param为路由定义一个获取博文slug的参数中间件,如下:

    // +++
    router.param('slug', (req, res, next, slug) => {
      Article.findOne({slug})
        //.populate('author')
        .then(article => {
          if (!article)
            return res.status(404).json({errors: {slug: `no such slug: ${slug}`}});
          res.locals.article = article;
          return next();
        })
        .catch(next);
    });
    // +++
    
    router.post('/', auth.required, loadCurrentUser, ...
    

    每当该路由遇到的URL中有和:slug对应的部分时,Express就会调用上述中间件,把截取到的数据传给第四个参数slug,然后从数据库中读取相应的博文,存给res.locals.article或者返回404。

    查阅博文

    端点为GET /api/articles/:slug,身份验证可有可无。

    routes/api/articles.js中写入:

    // +++
    router.get('/:slug', auth.optional, loadCurrentUser, (req, res, next) => {
      res.locals.article.populate('author')
        .execPopulate()
        .then(() => next())
        .catch(next);
    }, respondArticle);
    // +++
    

    Mongoose提供的populate()方法,允许一个文档(这里是个Article)读取关联的另一个文档(这里属于User)。

    更改博文

    端点是PUT /api/articles/:slug,身份验证是必需的,请求体的数据会覆盖相应字段。另外,我们还需确保当前登录的用户必须是该博文的作者,否则返回403

    routes/api/articles.js中写入:

    // +++
    const checkAuthor = (req, res, next) => {
      if (!res.locals.user.equals(res.locals.article.author))
        return res.status(403).json({errors: {user: 'not the author'}});
      return next();
    };
    
    router.put('/:slug', auth.required, loadCurrentUser, checkAuthor,
      (req, res, next) => {
        ['title', 'description', 'body'].forEach(propName => {
          if (!req.body.article.hasOwnProperty(propName)) return;
          res.locals.article[propName] = req.body.article[propName];
        });
        res.locals.article.save()
          .then(() => next())
          .catch(next);
      }, respondArticle);
    // +++
    
    module.exports = router;
    

    删除博文

    端点为DELETE /api/articles/:slug,需要身份验证,不需要请求体。同上,我们需要检查当前用户是否该博文的作者。如果删除成功,我们返回状态码为204且无响应体的响应。

    routes/api/articles.js中写入:

    // +++
    router.delete('/:slug', auth.required, loadCurrentUser, checkAuthor,
      (req, res, next) => {
        res.locals.article.remove()
          .then(() => res.sendStatus(204))
          .catch(next);
      });
    // +++
    
    module.exports = router;
    

    到此,博文增查改删就全部实现了。

    相关文章

      网友评论

          本文标题:“真实世界”全栈开发-3.8-博文的增查改删

          本文链接:https://www.haomeiwen.com/subject/ynyftftx.html