Permalink
Find file
08308b3 Dec 9, 2016
332 lines (264 sloc) 8.95 KB

4.10.1 留言模型设计

我们只需要留言的作者 id、留言内容和关联的文章 id 这几个字段,修改 lib/mongo.js,添加如下代码:

lib/mongo.js

exports.Comment = mongolass.model('Comment', {
  author: { type: Mongolass.Types.ObjectId },
  content: { type: 'string' },
  postId: { type: Mongolass.Types.ObjectId }
});
exports.Comment.index({ postId: 1, _id: 1 }).exec();// 通过文章 id 获取该文章下所有留言,按留言创建时间升序
exports.Comment.index({ author: 1, _id: 1 }).exec();// 通过用户 id 和留言 id 删除一个留言

4.10.2 显示留言

在实现留言功能之前,我们先让文章页可以显示留言列表。首先创建留言的模板,新建 views/components/comments.ejs,添加如下代码:

views/components/comments.ejs

<div class="ui grid">
  <div class="four wide column"></div>
  <div class="eight wide column">
    <div class="ui segment">
      <div class="ui minimal comments">
        <h3 class="ui dividing header">留言</h3>

        <% comments.forEach(function (comment) { %>
          <div class="comment">
            <span class="avatar">
              <img src="/img/<%= comment.author.avatar %>">
            </span>
            <div class="content">
              <a class="author" href="/posts?author=<%= comment.author._id %>"><%= comment.author.name %></a>
              <div class="metadata">
                <span class="date"><%= comment.created_at %></span>
              </div>
              <div class="text"><%- comment.content %></div>

              <% if (user && comment.author._id && user._id.toString() === comment.author._id.toString()) { %>
                <div class="actions">
                  <a class="reply" href="/posts/<%= post._id %>/comment/<%= comment._id %>/remove">删除</a>
                </div>
              <% } %>
            </div>
          </div>
        <% }) %>

        <% if (user) { %>
          <form class="ui reply form" method="post" action="/posts/<%= post._id %>/comment">
            <div class="field">
              <textarea name="content"></textarea>
            </div>
            <input type="submit" class="ui icon button" value="留言" />
          </form>
        <% } %>

      </div>
    </div>
  </div>
</div>

在文章页引入留言的模板片段,修改 views/post.ejs 为:

views/post.ejs

<%- include('header') %>

<%- include('components/post-content') %>
<%- include('components/comments') %>

<%- include('footer') %>

新建 models/comments.js,添加如下代码:

models/comments.js

var marked = require('marked');
var Comment = require('../lib/mongo').Comment;

// 将 comment 的 content 从 markdown 转换成 html
Comment.plugin('contentToHtml', {
  afterFind: function (comments) {
    return comments.map(function (comment) {
      comment.content = marked(comment.content);
      return comment;
    });
  }
});

module.exports = {
  // 创建一个留言
  create: function create(comment) {
    return Comment.create(comment).exec();
  },

  // 通过用户 id 和留言 id 删除一个留言
  delCommentById: function delCommentById(commentId, author) {
    return Comment.remove({ author: author, _id: commentId }).exec();
  },

  // 通过文章 id 删除该文章下所有留言
  delCommentsByPostId: function delCommentsByPostId(postId) {
    return Comment.remove({ postId: postId }).exec();
  },

  // 通过文章 id 获取该文章下所有留言,按留言创建时间升序
  getComments: function getComments(postId) {
    return Comment
      .find({ postId: postId })
      .populate({ path: 'author', model: 'User' })
      .sort({ _id: 1 })
      .addCreatedAt()
      .contentToHtml()
      .exec();
  },

  // 通过文章 id 获取该文章下留言数
  getCommentsCount: function getCommentsCount(postId) {
    return Comment.count({ postId: postId }).exec();
  }
};

小提示:我们让留言也支持了 markdown。

注意:其实通过 commentId 就可以唯一确定并删除一条留言,添加 author 的限制是为了防止用户删除他人的留言。 注意:删除一篇文章成功后也要删除该文章下所有的评论,上面 delCommentsByPostId 就是用来做这件事的。

修改 models/posts.js,在:

models/posts.js

var Post = require('../lib/mongo').Post;

下添加如下代码:

var CommentModel = require('./comments');

// 给 post 添加留言数 commentsCount
Post.plugin('addCommentsCount', {
  afterFind: function (posts) {
    return Promise.all(posts.map(function (post) {
      return CommentModel.getCommentsCount(post._id).then(function (commentsCount) {
        post.commentsCount = commentsCount;
        return post;
      });
    }));
  },
  afterFindOne: function (post) {
    if (post) {
      return CommentModel.getCommentsCount(post._id).then(function (count) {
        post.commentsCount = count;
        return post;
      });
    }
    return post;
  }
});

在 PostModel 上注册了 addCommentsCount 用来给每篇文章添加留言数 commentsCount,在 getPostByIdgetPosts 方法里的:

.addCreatedAt()

下添加:

.addCommentsCount()

这样主页和文章页的文章就可以正常显示留言数了。

然后将 delPostById 修改为:

// 通过用户 id 和文章 id 删除一篇文章
delPostById: function delPostById(postId, author) {
  return Post.remove({ author: author, _id: postId })
    .exec()
    .then(function (res) {
      // 文章删除后,再删除该文章下的所有留言
      if (res.result.ok && res.result.n > 0) {
        return CommentModel.delCommentsByPostId(postId);
      }
    });
}

小提示:虽然目前看起来使用 Mongolass 自定义插件并不能节省代码,反而使代码变多了。Mongolass 插件真正的优势在于:在项目非常庞大时,可通过自定义的插件随意组合(及顺序)实现不同的输出,如上面的 getPostById 需要将取出 markdown 转换成 html,则使用 .contentToHtml(),否则像 getRawPostById 则不使用。

修改 routes/posts.js,在:

routes/posts.js

var PostModel = require('../models/posts');

下引入 CommentModel:

var CommentModel = require('../models/comments');

在文章页传入留言列表,将:

// GET /posts/:postId 单独一篇的文章页
router.get('/:postId', function(req, res, next) {
  var postId = req.params.postId;

  Promise.all([
    PostModel.getPostById(postId),// 获取文章信息
    PostModel.incPv(postId)// pv 加 1
  ])
  .then(function (result) {
    var post = result[0];
    if (!post) {
      throw new Error('该文章不存在');
    }

    res.render('post', {
      post: post
    });
  })
  .catch(next);
});

修改为:

// GET /posts/:postId 单独一篇的文章页
router.get('/:postId', function(req, res, next) {
  var postId = req.params.postId;

  Promise.all([
    PostModel.getPostById(postId),// 获取文章信息
    CommentModel.getComments(postId),// 获取该文章所有留言
    PostModel.incPv(postId)// pv 加 1
  ])
  .then(function (result) {
    var post = result[0];
    var comments = result[1];
    if (!post) {
      throw new Error('该文章不存在');
    }

    res.render('post', {
      post: post,
      comments: comments
    });
  })
  .catch(next);
});

现在刷新文章页试试吧。

4.10.3 发表与删除留言

现在我们来实现发表与删除留言的功能。修改 routes/posts.js,将:

routes/posts.js

// POST /posts/:postId/comment 创建一条留言
router.post('/:postId/comment', checkLogin, function(req, res, next) {
  res.send(req.flash());
});

// GET /posts/:postId/comment/:commentId/remove 删除一条留言
router.get('/:postId/comment/:commentId/remove', checkLogin, function(req, res, next) {
  res.send(req.flash());
});

修改为:

// POST /posts/:postId/comment 创建一条留言
router.post('/:postId/comment', checkLogin, function(req, res, next) {
  var author = req.session.user._id;
  var postId = req.params.postId;
  var content = req.fields.content;
  var comment = {
    author: author,
    postId: postId,
    content: content
  };

  CommentModel.create(comment)
    .then(function () {
      req.flash('success', '留言成功');
      // 留言成功后跳转到上一页
      res.redirect('back');
    })
    .catch(next);
});

// GET /posts/:postId/comment/:commentId/remove 删除一条留言
router.get('/:postId/comment/:commentId/remove', checkLogin, function(req, res, next) {
  var commentId = req.params.commentId;
  var author = req.session.user._id;

  CommentModel.delCommentById(commentId, author)
    .then(function () {
      req.flash('success', '删除留言成功');
      // 删除成功后跳转到上一页
      res.redirect('back');
    })
    .catch(next);
});

上一节:4.9 文章

下一节:4.11 404页面