【问题标题】:Upvote and Downvote with Backbone, Express and Mongoose使用 Backbone、Express 和 Mongoose 投票和投票
【发布时间】:2013-07-28 02:44:00
【问题描述】:

我正在尝试实现一个类似于 stackoverflow 或 reddit 的投票系统,其中用户只能在给定的帖子上投票一次。

遵循此处给出的建议后

storing upvotes/downvotes in mongodb

我创建了两个模式来存储赞成票和反对票。对于每个用户,我都会跟踪用户投票的帖子。

发布架构:

var postSchema = new Schema({
    name: String,
    votes: Number,
    votetype: Number,
    postedBy: { type: String, ref: 'User' },
});

用户架构:

var userSchema = new Schema({
    twittername: String,
    twitterID: Number,
    votedPosts : [{ _id : mongoose.Schema.Types.ObjectId , votetype: Number }]
});

根据当前用户,每个帖子都会有不同的视图,如果用户在upvote 按钮或downvote 按钮变为橙色(类似于stackoverflow)之前对帖子进行了投票,那么我有以下(简化)帖子的主干模型:

var PostModel = Backbone.Model.extend({
    urlRoot : '/tweet',
    idAttribute: '_id',
    defaults:{
        name: '',
        votes: 0,
        votetype: 0,
        postedBy : '',
    },

    upvote: function(){
        this.set ({votetype : 1 }, {votes : this.get('votes') + 1});
        this.save();
        $.ajax({
            type: "POST",
            url:"/upvote",
            data : {postID : this.id , userID : window.userID , vote: 1},
            success : function(result){
                console.log(result);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log(textStatus, errorThrown);
            }

        });

    },


}); 

因此,如果用户之前没有对帖子投票,则 votetype 以“0”开头,根据投票情况,它的“1”或“-1”。在 upvote 函数中,当我更新并保存该帖子的投票类型时,我还发送一个 ajax 请求以将该帖子添加到帖子控制器中用户的投票帖子数组中,如下所示:

exports.upvote = function(req,res){
  var postID = req.body.postID;
  var newvotetype = req.body.vote;

  User.findOne({twitterID : req.body.userID}, {votedPosts : { $elemMatch: { "_id":  postID  }}},
         function(err, post) { 
              if (post.votedPosts.length == 0) { 
                //append to the array
                User.update({twitterID : req.body.userID} , { $push : {votedPosts : {_id : postID , votetype: newvotetype}}} ,function (err, user, raw) {
                    if (err){console.log(err);}
                });

                console.log(post);
                console.log("no one has voted on this before");

              } 
              else { 
                //update in the existing array
                User.update({twitterID : req.body.userID, 'votedPosts._id':  postID  }, { $set : {'votedPosts.$.votetype' : newvotetype}} ,function (err, user, raw) {
                    if (err){console.log(err);}
                });
              }
          }
  );
  res.send("success");
  res.end();
}; 

我可能有一些糟糕的设计决定,但到目前为止,这似乎工作正常。请告诉我是否可以对我的代码或我的设计进行任何改进。

现在是棘手的部分。在执行 collection.fetch() 之前,我必须以某种方式查看这两种模式并更改每个帖子的“投票类型”。我想出了一个像这样的丑陋解决方案:

https://gist.github.com/gorkemyurt/6042558

(我把它放在一个 gits 中,所以它可能更具可读性,对于丑陋的代码感到抱歉..)

一旦我根据用户更新每个帖子的投票类型,我会将其传递给我的主干视图,并在我的模板中执行一些非常基本的操作,例如:

<div class="post-container">
      <div id="arrow-container">
            <% if (votetype == 1 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up-orange.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down.jpg"></p>
            <% } %>
            <% if ( votetype  == 0 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down.jpg"></p>
            <% } %>
            <% if ( votetype  == -1 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down-orange.jpg"></p>
            <% } %>
      </div>

      <div id="text-container">
            <p><h2><%- name %></h2></p>
            <p><%- dateCreated %></p>
            <p>Posted by: <%- postedBy %></p>
      </div>
</div>

此解决方案有效,但我认为每次用户打开页面以呈现帖子的自定义视图时查找所有帖子和用户投票的所有帖子并不是真正有效的。任何人都可以想到更好的方法来做到这一点?我愿意接受有关我的代码的任何建议或批评。在此先感谢

【问题讨论】:

    标签: node.js mongodb backbone.js mongoose database-schema


    【解决方案1】:

    有很多地方可以改进:

    首先,您的客户端代码对于攻击者来说是唾手可得的成果 - 您使用两个请求执行原子操作(upvote/downvote),第一个请求不仅发送投票类型,还发送投票总数:

    this.set ({votetype : 1 }, {votes : this.get('votes') + 1});
    this.save();
    // btw this looks broken, the second argument for `set` is options, so 
    // if you want to set votes, you should move them to the first argument:
    this.set ({votetype : 1, votes : this.get('votes') + 1});
    

    但是,如果攻击者发送 100 甚至 1000 票,您的应用程序将如何响应? 这个操作应该是原子的,当您向/upvote 端点发出 POST 请求时,您应该增加服务器上的投票。

    其次,您实际上并不需要在帖子本身上存储 votetype - 每当用户投票时,您都会更改对所有用户可见的 votetype,但您稍后会使用循环将其隐藏,并且存储一个 votetype 很奇怪对帖子的最后一次投票,您显然需要拥有特定用户的投票类型,因此,您在架构中不需要它并且可以远程它。 您可以从帖子中删除投票类型,并且可以通过在帖子本身上存储投票历史记录来删除循环,因此无论何时要显示帖子或帖子列表,您都可以轻松过滤数组以仅包含给定的投票用户,因此您的架构将如下所示:

    var postSchema = new Schema({
       name: String,
       votesCount: Number,
       votes: [{ user_id : mongoose.Schema.Types.ObjectId , type: Number }],
       postedBy: { type: String, ref: 'User' },
    });
    

    然后您可以通过以下方式获得帖子并过滤投票:

    Post.findOne({_id:<post-id>)}, function(err, post){
         post.vote = post.votes.filter(function(vote){
             return vote.user_id === req.body.userID;
         })[0].type;
         res.send(post)
      }
    )
    // or list of posts
    Post.find(function(err, posts){
         posts.forEach(function(post){
             post.vote = post.votes.filter(function(vote){
                 return vote.user_id === req.body.userID;
             })[0].type;
         });
         res.send(posts)
      }
    )
    // you can move vote finding logic in a function: 
    function findVote(post) {
        var vote = post.votes.filter(function(vote){
            return vote.user_id === req.body.userID;
        })[0]
        if(vote) return vote.type;
    }
    

    如果您需要在用户个人资料中显示最新投票的帖子,您可以过滤用户投票的帖子:

    Post.find({'votes.user_id': req.body.userID}, function(err, posts){
         posts.forEach(function(post){
             // using findVote defined above
             post.vote = findVote(post);
         });
         res.send(posts)
      }
    )
    

    客户端的模板代码应该几乎保持不变。

    【讨论】:

    • 非常感谢您的回答,这真的很有帮助。我只是想学习..你能否再次澄清这部分“但是,如果攻击者发送 100 甚至 1000 票,你的应用程序将如何响应?这个操作应该是原子的,你应该在你做的时候增加服务器上的投票向 /upvote 端点发送 POST 请求。”
    • 是的,从你写的内容来看,我得出的结论是你提出了两个请求:一个当你保存PostModel时:this.save();当您制作POST /upvote 时,紧随其后的是一个。您提出这些请求是为了分配用户投票,并且与其他任何地方一样,您需要确保用户无法利用它。如果某个用户只发送第一个请求 - 增加投票的请求,而不发送第二个请求 - 它基本上使他们的投票匿名。正确的方法是在模型中分配新值而不调用this.save,并从/upvote 上的 your 服务器端代码写入数据库
    • 您将如何添加或更改我的投票?根据我目前对 MongoDB 的理解,我必须首先查看是否有用户投票,然后添加或更改投票。但是,如果在检查是否有投票后为他添加另一票会发生什么?
    • 好吧,在将用户投票保存到数组之前,您应该检查用户是否已经对帖子投票,您可以在获取文档后简单地过滤数组,或者您可以使用 $elemMatch 查询包含特定用户投票的帖子,如果返回文档 - 什么也不做。例如db.posts.find({_id:&lt;post-id&gt;}, { votes: { $elemMatch: { _id: &lt;user-id&gt; } } })
    • 或者你可以简单地使用 $addToSet docs.mongodb.org/manual/reference/operator/addToSet
    猜你喜欢
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 2012-10-29
    • 1970-01-01
    • 2011-09-01
    • 1970-01-01
    相关资源
    最近更新 更多