9:发布
现在我们已将所有应用程序的敏感代码移至方法中,我们需要了解 Meteor 安全体系的另一半。到目前为止,我们一直假设整个数据库都存在于客户端,这意味着如果我们调用 Tasks.find()
,我们将获取集合中的每个任务。如果应用程序的用户想要存储隐私敏感数据,这并不好。我们需要一种方法来控制 Meteor 发送到客户端数据库的数据。
9.1:autopublish
就像上一步中的 insecure
一样,所有新的 Meteor 应用程序都从 autopublish
包开始,该包会自动将所有数据库内容同步到客户端。因此,您应该将其删除。
meteor remove autopublish
当应用程序刷新时,任务列表将为空。如果没有 autopublish
包,我们将必须明确指定服务器发送到客户端的内容。Meteor 中执行此操作的函数是 Meteor.publish
和 Meteor.subscribe
Meteor.publish
:允许将数据从服务器发布到客户端;Meteor.subscribe
:允许客户端代码请求数据到客户端。
9.2:Tasks 发布
您需要首先向服务器添加一个发布,此发布应发布来自已认证用户的全部任务。与 Methods
一样,您也可以在发布函数中使用 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:Tasks 订阅
然后,我们可以在客户端订阅该发布。
由于我们希望接收来自此发布的更改,因此我们将使用 $subscribe
在我们的 meteor
对象中对其进行 订阅
。有关更多信息,请参阅 文档。
imports/ui/App.vue
..
meteor: {
$subscribe: {
'tasks': []
},
tasks() {
..
}
..
9.4:加载状态
您还应该为您的应用程序添加加载状态,这意味着,在订阅数据未准备好时,您应该通知用户。要发现订阅是否已准备好,您应该获取 subscribe
调用的返回值,它是一个包含订阅状态的对象,包括 ready
函数,该函数将返回一个 boolean
值。
imports/ui/App.vue
..
</div>
<div class="loading" v-if="!$subReady.tasks">Loading...</div>
<ul class="tasks">
..
让我们也稍微设置一下此加载的样式。
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,
},
});
},
..
如果我们没有在客户端返回其他用户的任务,为什么这很重要?
这很重要,因为任何人都可以使用浏览器 console
调用 Meteor Methods
。您可以使用 DevTools 控制台选项卡进行测试,然后键入并按 Enter:Meteor.call('tasks.remove', 'xtPTsNECC3KPuMnDu');
。如果您从删除方法中删除验证并传递数据库中一个有效的任务 _id
,则您将能够将其删除。
回顾:您可以在此步骤结束时查看代码应如何编写 此处
在下一步中,我们将作为原生应用在移动环境中运行该应用。