11

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

遵循此处给出的建议后

在 mongodb 中存储 upvotes/downvotes

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

发布模式:

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>

该解决方案有效,但我认为每次用户打开页面以呈现帖子的自定义视图时查找所有帖子和用户投票的所有帖子并不是很有效.. 谁能想到更好的怎么做?我愿意接受有关我的代码的任何建议或批评。在此先感谢

4

1 回答 1

7

有很多地方可以改进:

首先,您的客户端代码对于攻击者来说是唾手可得的成果 - 您使用两个请求执行原子操作(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)
  }
)

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

于 2013-07-27T13:39:25.357 回答