【问题标题】:Publishing structured, reactive data to the client (outside of database collections)向客户端发布结构化的响应式数据(在数据库集合之外)
【发布时间】:2015-11-18 07:07:51
【问题描述】:

我正在寻找最高效的解决方案,用于根据请求在 Meteor 框架中向客户端发送结构化数据。

问题: 有时,在将数据从数据库发送到客户端之前,您希望添加一些服务器端生成的附加信息,这些信息正在发送到客户端(即许多对象的安全凭证)。此数据可能是时间关键的(即由于过期时间戳),因此不应存储在数据库中。此外,有时无法在客户端处理此数据(即由于安全原因)。在许多情况下,这些数据在结构上与实际数据库数据相关,但也与单个请求密切相关,因为您可能希望将其丢弃并在新请求中重新生成。

你可以(至少在设计上......):

  1. 创建第二个集合,在那里存储和发布与请求相关的数据,接受写入开销,然后在 Meteor.myTemplate.destroyed=function(){...} 中再次删除数据,接受另一个写入开销。

  2. 将每个条目存储在会话变量中,但是您还必须小心 稍后删除它(Meteor.myTemplate.destroyed=function(){...}),这是我现在最喜欢的,但我在存储大对象时遇到了问题。

  3. 将此数据存储在 dom 中(即在隐藏或可见元素的属性或数据字段中)
  4. 通过在 dom 中存储适当的 arguments 并使用 callback(){...} 中的 jQuery 将它们注入回来,使用 Meteor.call('method',arguments,callback(){...}) 从 dom 生成此数据。

你不能:(按设计!!)

  1. 在服务器上的Meteor.publish("name",function(){...}) 中使用转换
  2. Template.variable=function(){return collection.find(...)} 上的转换中使用Meteor.call()(如果您在客户端上有相应的Meteor.method() 来猜测结果,也不要这样做!)。

同样,我正在寻找的是性能最佳的解决方案。

【问题讨论】:

    标签: javascript performance meteor


    【解决方案1】:

    为了解决我关心的转换问题,因为我认为就智能模型做事而不是一堆匿名函数而言,这是一个服务器转换到达客户端的示例(不是响应式,通过调用,不是发布,而是说明正在讨论的服务器转换问题)。

    你会得到这个:

    每个图像都是本地的 数据:LOLCATZ RULZ 转换: 数据:LOLCATZ RULZ 转换: 每个图像服务器转换 数据:LOLCATZ RULZ 变换:XYZ 数据:LOLCATZ RULZ 变换:XYZ

    来自:

    <template name='moritz'>
        <h3>each image local</h3>
        <dl>
            {{#each images}}
            <dt>data: {{caption}}</dt>
            <dd>transform: {{secretPassword}}</dd>
            {{/each}}
        </dl>
    
        <h3>each image server transformed</h3>
        <dl>
            {{#each transformed}}
            <dt>data: {{caption}}</dt>
            <dd>transform: {{secretPassword}}</dd>
            {{/each}}
        </dl>
    </template>
    
    if (Meteor.isServer) {
    
        Images = new Meteor.Collection('images', {
            transform: function (doc) {
                doc.secretPassword = 'XYZ'
                return doc
            }
        });
    
        Images.allow({
            insert: function (userid, doc) {
                return true;
            }
        });
    
        if (Images.find().count() < 1) {
            Images.insert({ caption: 'LOLCATZ RULZ'});
        }
    
        Meteor.publish('images', function () {
            return Images.find();
        })
    
        Meteor.methods({
            'transformed': function() {
                return Images.find().fetch();
            }
        })
    }
    else {
        Images = new Meteor.Collection('images');
    
        imageSub = Meteor.subscribe('images');
    
        Template.moritz.helpers({
            'images': function () {
                console.log(Images.find().count() + ' images')
                return Images.find();
            },
            'transformed': function () {
                // Should be separated, call should be in route for example
                Meteor.call('transformed', function(err,data){
                   Session.set('transformed', data);     
                    });
                return Session.get('transformed');
            }
        });
    }  
    

    【讨论】:

    • 刚刚测试过,我可以确认它就像一个魅力。我什至没有尝试过这样的事情!感谢您分享这个很酷的解决方案!
    • :) 我正在使用转换到一个包含方法的类,并删除了我的大部分助手。但是你必须在客户端上进行转换。
    • 哦,伙计,你肯定救了我,现在这很优雅。我什至可以在创建集合时跳过“转换”,我只是将它直接放在Meteor.call(...) 内的find({},{...}) 中,以便灵活地转换它。惊人的!!谢谢!!
    • 嗨,Jim,看看我下面的例子,我拿走了你的代码并添加了响应性。只要我有 15 名声望,我就会给你一个应得的支持,我在这里仍然是一个 12 个代表的低人 ;)
    【解决方案2】:

    看看Meteor Streams,你可以从服务器直接向客户端发送一些东西,而不必在客户端使用集合。

    您可以对收到的消息做一些事情(我使用的是来自流星流网站的示例):

    客户

    chatStream.on('message', function(message) {
        if(message.expiry > new Date()) {
            //Do something with the message (not being read from a collection)
        }
    });
    

    即使您这样做是为了不存储它,但要小心简单的工具(Chrome Inspector)可能会进入“网络/Websocket”选项卡(即使它通过 SSL 加密)并看到正在传递的原始数据.

    虽然我不确定您的意图,但如果这是出于任何情况下的安全考虑,请永远不要相信您从客户端获得的任何数据。

    【讨论】:

    • 是的 Meteor Streams 看起来很不错,看起来它可以用于很多用途。非常感谢您的提示!我不关心 Chrome Inspector,我的目的是在服务器上生成策略,在服务器上使用密钥对其进行签名并将它们发送到客户端。它们将与我存储在常规 monogdb 集合中的 s3 存储桶中的文件的链接相关。然而,这些策略将过期,这就是为什么应该“按需”创建而不是存储的原因,因为它们只被检索一次并且只是数据库中的开销数据。
    • 好的,我明白了,所以 Session var 或 Collection 对此没有太大的影响。渲染问题通过使用观察者回调而不是渲染回调来解决。现在工作速度很快!仍然是实际发布功能不向客户端发送转换的解决方法。
    【解决方案3】:

    “Mongo 收藏”似乎可以很好地回答您的第一行,但随后我读到了一些相互矛盾的想法和结论,我不确定我是否同意。 (例如,你为什么不能做这两件事?因为它必须发生在服务器上?)我最困惑的说法是:

    此数据可能是时间关键的(即由于过期时间戳),因此不应存储在数据库中。

    我不明白这个结论的假设是什么,但是如果您认为在 dom 中存储数据是合理的,根据上面的第 2 点和第 3 点,您似乎对性能远低于 mongo db 的系统持开放态度.

    您知道,您可以从服务器发布额外的服务器生成的计算的第二个集合,这些计算是您从数据中动态生成的,并在客户端上将其与数据放在一起。有点像父子关系:当您显示客户端文档时,从动态集合中提取来自服务器的附加数据并将其处理到您的模板中。

    【讨论】:

    • 第一:据我所知,“你不能”列表中的所有东西都可能不是(香草)流星的设计。当然,您可以创建第二个集合,我会将其添加到“您可以”列表中。但是,出于安全和开销原因,我真的在寻找一种不将数据存储在数据库中的解决方案。更多类似Meteor.call() 的东西,在Template.myTemplate.variable=function(){...}Meteor.myTemplate.created=function(){...} 中调用时只返回服务器生成的值,谢谢您的输入!
    • 好的,不能 #1:我在服务器上使用了转换。如果它们包含函数,您就不能使用 eJson 将转换的对象流式传输到客户端。如果我理解您所写的内容,我还在“不能 2”的场景中使用转换,因此可能有语法说明。
    • 另外,请注意过早的优化。您的时间规划也是等式的一部分,并且在您对整个系统进行测量之前保持标准是一种经过验证的真实方法。自己动手可能会更有趣,但你能证明在这个阶段有需要吗?
    • 嘿,Jim,毫无疑问,转换在服务器上有效,但在不能#1 我声明它们在返回对象的Meteor.publish("name",function(){...}) 中无效。这是设计使然,并且是有意义的,因为发布的集合与客户端集合同步,而 Meteor 同步的想法对于服务器上的转换毫无意义。
    • 证明需要.. 因此,如果您通过 http 使用 filepicker 或 s3,您可能会将 InkBlobs/s3-Links 存储在您的数据库中,并在需要时将它们发布到客户端。如果您使用安全模型,那么您还必须发送安全凭证。在我看来,这些凭据是结构化数据,与数据库和请求相关,需要发布但不存储在数据库中。这有任何意义吗?我不是在这里要求一个非标准的解决方案,只是标准内的最佳实施。现在我使用Meteor.call() 发送数据并将其存储在 minimongo 中。
    【解决方案4】:

    Jim Mack 在这里创建了一个很好的示例,它证明了在 Session 变量中存储 db 数据以及其他“转换”属性的效果如何。 不幸的是,这个例子缺乏反应性,并且在 Meteor 的魔法重新渲染后没有执行所需的“转换”。所以我抓住了他很酷的代码并添加了响应性,这是一个运行良好的苗条代码,但在效率方面将优于 Jim Mack 的示例。

    lolz.html

    <head>
      <title>lolz</title>
    </head>
    
    <body>
      {{>myItems}}
    </body>
    
    <template name="myItems">
        <h3>Reactive Item List with additional properties</h3>
        <button id="add">add</button>
        <button id="remove">remove</button>
        <dl>
            {{#each items}}
                <dt>data: {{caption}}</dt>
                <dd>added property: {{anotherProp _id}}</dd>
            {{/each}}
        </dl>
    </template>
    

    lolz.js

    items = new Meteor.Collection('Items');
    
    if (Meteor.isServer) {
        items.allow({
        insert: function (userid, doc) {
            return true;
        },
        remove: function(userid,doc){
            return true;
        }
      });
      while(items.find().count()>0){
        items.remove(items.findOne()._id);
      }
      while (items.find().count() < 3) {
            items.insert({caption: 'LOLCATZ RULZ'});
        }
      Meteor.publish('Items', function () {
          return items.find();
      });
      Meteor.methods({
            'getAdditionalProps': function() {
                additionalProps={};
            items.find().forEach(function(doc){
                additionalProps[doc._id]=reverse(doc.caption);
          });
          return additionalProps;
        }
      });
    
        function reverse(s){ // server side operation, i.e. for security reasons
        return s.split("").reverse().join("");
        };
    }
    
    if (Meteor.isClient){
        Meteor.subscribe('Items');
    
        Meteor.startup(function(){
            getAdditionalProps();
            itemsHandle=items.find().observe({
                added : function(doc){
                    getAdditionalProps();
                },
                removed : function(doc){
                    getAdditionalProps();
                },
                changed : function(docA,docB){
                    getAdditionalProps();
                }
            });
        });
    
        Template.myItems.rendered=function(){
            console.log(new Date().getTime());
        };
    
        Template.myItems.items=function(){
            return items.find();
        }
    
        Template.myItems.anotherProp=function(id){
            return Session.get('additionalProps')[id];
        }
    
        Template.myItems.events({
            'click #add':function(e,t){
                items.insert({caption: 'LOLCATZ REACTZ'});
            },
            'click #remove':function(e,t){
                items.remove(items.findOne()._id);
            }
        });
    }
    
    function getAdditionalProps(){
        setTimeout(function(){
            Meteor.call('getAdditionalProps',function(error,props){
                Session.set('additionalProps',props);
            });
        },0);
    }
    

    【讨论】:

    • 所以,我会将额外的道具作为单独的集合,在这种情况下不会在会话中存储任何内容。我知道你已经表达了为什么不这样做的理由。我认为我最强烈的论点是它利用了 Meteor 的一个核心特性,他们尽可能地对其进行优化。
    • 非常正确,只是在这种情况下,我认为我真的不会错过 Meteor Collection 的功能,它更像是一种将一些缺少的功能同步到 Collection 本身的解决方法。而且它非常低效地导致重新渲染。我仍在尝试解决此问题,但我遇到了 Meteor 观察者工具的另一个问题:stackoverflow.com/questions/18645334
    • 嘿,如果您的刷新速度很慢,您是否在每个文档的元素周围都有一个带有 id 的 div?喜欢
    • 刷新速度很快,但发生得太频繁了。当列表变长时,我会得到十几个重新渲染,每个项目都添加到集合中。我已经把它放在集线器上:github.com/iamnotachoice/meteor-transform-on-publish-workaround
    • 我对您遇到问题的代码更感兴趣,而不是您为解决问题而编写的代码。我仍然认为您的重新渲染问题不是 Meteor 的设计缺陷。你见过projectricochet.com/blog/meteor-js-performance#.UilnIlFDulg
    猜你喜欢
    相关资源
    最近更新 更多
    热门标签