9: 发布

现在我们已经将应用程序的所有敏感代码移到了方法中,我们需要了解 Meteor 安全体系的另一半。我们一直假设整个数据库都存在于客户端,这意味着如果我们调用 Tasks.find(),我们将获取集合中的每个任务。如果我们的应用程序用户想要存储私密和敏感数据,这可不是好事。我们需要控制 Meteor 发送到客户端数据库的数据。

9.1: autopublish

就像上一步中的 insecure 一样,所有新的 Meteor 应用都从 autopublish 包开始,该包会自动将所有数据库内容同步到客户端。使用下面的命令行将其删除

meteor remove autopublish

当应用程序刷新时,任务列表将为空。如果没有 autopublish 包,我们将必须明确指定服务器发送到客户端的内容。Meteor 中执行此操作的函数是 Meteor.publishMeteor.subscribe

  • Meteor.publish:允许将数据从服务器发布到客户端;
  • Meteor.subscribe:允许客户端代码请求数据。

9.2: 任务发布

您需要首先向服务器添加一个发布。此发布应发布所有经过身份验证的用户的所有任务。与 方法 中一样,您也可以在发布函数中使用 this.userId 获取经过身份验证的 userId

api 文件夹中创建一个名为 tasksPublications.js 的新文件。

imports/api/tasksPublications.js

import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/db/TasksCollection';

Meteor.publish('tasks', function publishTasks() {
  return TasksCollection.find({ userId: this.userId });
});

由于您在该函数内部使用了 this,因此您不应使用箭头函数 (=>),因为箭头函数不提供 this 的上下文。您需要以传统方式使用该函数,使用 function 关键字。

最后一步是确保您的服务器正在注册此发布。您可以通过导入该文件来强制在 server/main.js 中进行评估。

server/main.js

import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
import '/imports/api/tasksPublications';

9.3: 任务订阅

从这里,我们可以在客户端订阅该发布。

由于我们希望接收来自此发布的更改,因此我们将在 Tracker.autorun 内部对其进行 订阅

Tracker.autorun 现在运行一个函数,并在其依赖项发生更改时稍后重新运行它,这对我们了解何时准备好将数据显示给用户非常完美。您可以在这里了解更多关于 tracker 包的信息 here

imports/ui/App.js

...
const IS_LOADING_STRING = "isLoading";
...

Template.mainContainer.onCreated(function mainContainerOnCreated() {
  this.state = new ReactiveDict();

  const handler = Meteor.subscribe('tasks');
  Tracker.autorun(() => {
    this.state.set(IS_LOADING_STRING, !handler.ready());
  });
});

...

Template.mainContainer.helpers({
  ...,
  isLoading() {
    const instance = Template.instance();
    return instance.state.get(IS_LOADING_STRING);
  }
});

...

9.4: 加载状态

现在我们可以向用户显示数据何时正在加载。让我们使用新的辅助函数来显示它

imports/ui/App.html

...
                <div class="filter">
                    <button id="hide-completed-button">
                        {{#if hideCompleted}}
                                Show All
                        {{else}}
                                Hide Completed
                        {{/if}}
                    </button>
                </div>

                {{#if isLoading}}
                    <div class="loading">loading...</div>
                {{/if}}
...

我们也来稍微设置一下这个加载的样式

client/main.css

.loading {
  display: flex;
  flex-direction: column;
  height: 100%;

  justify-content: center;
  align-items: center;

  font-weight: bold;
}

完成后,所有任务将重新出现。

在服务器上调用 Meteor.publish 会注册一个名为 tasks 的发布。当在客户端上使用发布名称调用 Meteor.subscribe 时,客户端会订阅来自该发布的所有数据,在本例中是数据库中经过身份验证的用户的所有任务。

9.5: 检查用户权限

只有任务的所有者才能更改某些内容。您应该更改您的方法以检查经过身份验证的用户是否与创建任务的用户相同。

imports/api/tasksMethods.js

..
  'tasks.remove'(taskId) {
    check(taskId, String);

    if (!this.userId) {
      throw new Meteor.Error('Not authorized.');
    }

    const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });

    if (!task) {
      throw new Meteor.Error('Access denied.');
    }

    TasksCollection.remove(taskId);
  },

  'tasks.setIsChecked'(taskId, isChecked) {
    check(taskId, String);
    check(isChecked, Boolean);

    if (!this.userId) {
      throw new Meteor.Error('Not authorized.');
    }

    const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });

    if (!task) {
      throw new Meteor.Error('Access denied.');
    }

    TasksCollection.update(taskId, {
      $set: {
        isChecked,
      },
    });
  },
..

如果我们没有在客户端返回其他用户的任务,为什么这很重要?

因为任何人都可以使用浏览器 控制台 调用 Meteor 方法。您可以使用 DevTools 控制台选项卡进行测试,然后键入:Meteor.call('tasks.remove', 'xtPTsNECC3KPuMnDu'); 并按 Enter 键。如果您从删除方法中删除验证并传递数据库中一个有效的任务 _id,您将能够删除它。

回顾:您可以查看本步骤结束时代码应如何编写 here

在下一步中,我们将应用程序作为原生应用程序在移动环境中运行。

在 GitHub 上编辑
// 搜索框