16

好的,还是在我的玩具应用程序中,我想在一组车主的里程表上找出平均里程数。这在客户端上非常容易,但无法扩展。正确的?但是在服务器上,我不完全了解如何完成它。

问题:

  1. 你如何在服务器上实现一些东西然后在客户端上使用它?
  2. 如何使用 mongo 的 $avg 聚合函数来利用其优化的聚合函数?
  3. 或者替代(2)你如何在服务器上进行映射/减少并使其对客户端可用?

@HubertOG 的建议是使用 Meteor.call,这是有道理的,我这样做了:

# Client side
Template.mileage.average_miles = ->
  answer = null
  Meteor.call "average_mileage", (error, result) ->
    console.log "got average mileage result #{result}"
    answer = result
  console.log "but wait, answer = #{answer}"
  answer

# Server side
Meteor.methods average_mileage: ->
  console.log "server mileage called"
  total = count = 0
  r = Mileage.find({}).forEach (mileage) ->
    total += mileage.mileage
    count += 1
  console.log "server about to return #{total / count}"
  total / count

这似乎可以正常工作,但事实并非如此,因为据我所知Meteor.call,这是一个异步调用,并且answer总是返回空值。在服务器上处理东西似乎是一个很常见的用例,我一定只是忽略了一些东西。那会是什么?

谢谢!

4

4 回答 4

29

从 Meteor 0.6.5 开始,集合 API 还不支持聚合查询,因为没有(直接的)方法可以对它们进行实时更新。但是,您仍然可以自己编写它们,并在 a 中提供它们Meteor.publish,尽管结果是静态的。在我看来,这样做仍然更可取,因为您可以合并多个聚合并使用客户端集合 API。

Meteor.publish("someAggregation", function (args) {
    var sub = this;
    // This works for Meteor 0.6.5
    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    // Your arguments to Mongo's aggregation. Make these however you want.
    var pipeline = [
        { $match: doSomethingWith(args) },
        { $group: {
            _id: whatWeAreGroupingWith(args),
            count: { $sum: 1 }
        }}
    ];

    db.collection("server_collection_name").aggregate(        
        pipeline,
        // Need to wrap the callback so it gets called in a Fiber.
        Meteor.bindEnvironment(
            function(err, result) {
                // Add each of the results to the subscription.
                _.each(result, function(e) {
                    // Generate a random disposable id for aggregated documents
                    sub.added("client_collection_name", Random.id(), {
                        key: e._id.somethingOfInterest,                        
                        count: e.count
                    });
                });
                sub.ready();
            },
            function(error) {
                Meteor._debug( "Error doing aggregation: " + error);
            }
        )
    );
});

以上是一个示例分组/计数聚合。一些注意事项:

  • 当您这样做时,您自然会进行聚合server_collection_name并将结果推送到另一个名为client_collection_name.
  • 此订阅不会生效,并且可能会在参数更改时更新,因此我们使用一个非常简单的循环,将所有结果推送出去。
  • 聚合的结果没有 Mongo ObjectID,所以我们自己生成一些任意的。
  • 聚合的回调需要包装在 Fiber 中。我Meteor.bindEnvironment在这里使用,但也可以使用 aFuture进行更底层的控制。

如果您开始组合这些出版物的结果,您需要仔细考虑随机生成的 id 如何影响合并框。然而,一个简单的实现只是一个标准的数据库查询,除了在客户端使用 Meteor API 更方便。

TL;DR 版本:几乎在您从服务器推出数据的任何时候,a 都比 apublish更可取method

有关进行聚合的不同方法的更多信息,请查看这篇文章

于 2013-09-18T23:52:30.103 回答
2

我用“聚合”方法做到了这一点。(版本 0.7.x)

if(Meteor.isServer){
Future = Npm.require('fibers/future');
Meteor.methods({
    'aggregate' : function(param){
        var fut = new Future();
        MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){
            fut.return(result);
        });
        return fut.wait();
    }
    ,'test':function(param){
        var _param = {
            pipe : [
            { $unwind:'$data' },
            { $match:{ 
                'data.y':"2031",
                'data.m':'01',
                'data.d':'01'
            }},
            { $project : {
                '_id':0
                ,'project_id'               : "$project_id"
                ,'idx'                      : "$data.idx"
                ,'y'                        : '$data.y'
                ,'m'                        : '$data.m'
                ,'d'                        : '$data.d'
            }}
        ],
            collection:"yourCollection"
        }
        Meteor.call('aggregate',_param);
    }
});

}

于 2014-02-11T12:41:37.107 回答
1

您可以为此使用Meteor.methods

// server
Meteor.methods({
  average: function() {
    ...
    return something;
  },

});

// client

var _avg = {                      /* Create an object to store value and dependency */
  dep: new Deps.Dependency();
};

Template.mileage.rendered = function() {
  _avg.init = true;
};

Template.mileage.averageMiles = function() {
  _avg.dep.depend();              /* Make the function rerun when _avg.dep is touched */
  if(_avg.init) {                 /* Fetch the value from the server if not yet done */
    _avg.init = false; 
    Meteor.call('average', function(error, result) {
      _avg.val = result;
      _avg.dep.changed();         /* Rerun the helper */
    });
  }
  return _avg.val;
});
于 2013-08-29T21:35:39.417 回答
1

如果您想要反应性,请使用Meteor.publish而不是Meteor.call. 文档中有一个示例,他们发布了给定房间中的消息数量(就在文档的上方this.userId),您应该能够做类似的事情。

于 2013-08-30T07:25:52.703 回答