9: 发布

现在我们已经将应用程序的所有敏感代码移到了方法中,我们需要了解 Meteor 安全故事的另一半。到目前为止,我们一直假设整个数据库都存在于客户端,这意味着如果我们调用 TasksCollection.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: 任务订阅

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

由于我们希望接收来自此发布的更改,因此我们将在 $m 内部对其进行 订阅。通常,您可以使用 useTracker 来执行此操作,但对我们来说,由于我们使用的是 zodern:melte 编译器,因此没有必要。

imports/ui/App.svelte

<script>
    ..
    let isLoading = true;
    const handler = Meteor.subscribe('tasks');

    $m: {
        user = Meteor.user();

        if (user) {

            isLoading = !handler.ready();

            const userFilter = { userId: user._id };
            const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilter };


            tasks = TasksCollection.find(
                    hideCompleted ? pendingOnlyFilter : userFilter,
                    { sort: { createdAt: -1 } }
            ).fetch();

            incompleteCount = TasksCollection.find(pendingOnlyFilter).count();

            pendingTasksTitle = `${
                    incompleteCount ? ` (${incompleteCount})` : ''
            }`;
        }
    }
    ..
</script>
..

9.4: 加载状态

您还应该为您的应用程序添加一个加载状态,这意味着,在订阅数据未准备好之前,您应该通知用户。要发现订阅是否已准备好,您应该获取 subscribe 调用的返回值,它是一个包含订阅状态的对象,包括 ready 函数,该函数将返回一个 布尔值

imports/ui/App.svelte

..
        <div class="filter">
            <button on:click={() => setHideCompleted(!hideCompleted)}>
            {hideCompleted ? 'Show All' : 'Hide Completed'}
            </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 控制台选项卡进行测试,然后键入并按 Enter:Meteor.call('tasks.remove', 'xtPTsNECC3KPuMnDu');。如果您从删除方法中删除验证并传递数据库中的一个有效任务 _id,则您将能够删除它。

回顾:您可以检查代码在本步骤结束时应如何显示 这里

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

在 GitHub 上编辑
// 搜索框