5. 编写变更解析器
10m

GraphQL 变更如何修改数据

我们已经为 需要适用于 的模式 编写了所有。现在,让我们为我们模式的 变更编写解析器。这一过程几乎完全相同。

login

首先,让我们为 编写 Mutation.login,它使用户能够登录我们的应用程序。在 下方的 Query 添加以下代码:

server/src/resolvers.js
// Query: {
// ...
// },
Mutation: {
login: async (_, { email }, { dataSources }) => {
const user = await dataSources.userAPI.findOrCreateUser({ email });
if (user) {
user.token = Buffer.from(email).toString('base64');
return user;
}
},
},

获取 email 地址并从我们的 userAPI 返回相应用户数据。我们向对象添加 token 以表示用户的活动会话。在后面的章节中,我们将了解如何将此返回用户数据持久存储在我们的应用程序客户端中。

对已登录用户进行身份验证

示例应用程序中使用的认证方法一点也不安全并且不应在实际应用程序中使用。但是,您可以将下方介绍的原则应用到 安全的令牌认证方法中。

返回的 User 对象包含 token,客户端可使用该 token 对其自身在我们服务器上进行认证。现在,我们需要向我们的服务器添加逻辑以实际执行认证。

src/index.js 中,导入 isEmail 函数并将 context 函数传递给 ApolloServer 构造函数,与以下内容相符:

server/src/index.js
const isEmail = require("isemail");
const server = new ApolloServer({
context: async ({ req }) => {
// simple auth check on every request
const auth = (req.headers && req.headers.authorization) || "";
const email = Buffer.from(auth, "base64").toString("ascii");
if (!isEmail.validate(email)) return { user: null };
// find a user by their email
const users = await store.users.findOrCreate({ where: { email } });
const user = (users && users[0]) || null;
return { user: { ...user.dataValues } };
},
// Additional constructor options
});

上文定义的 context 函数在 对我们服务器发送的每个 GraphQL 操作 时会调用一次。此函数的返回值将会成为 context 参数,其作为该 的一部分运行时的每个 都已经传递给该参数。

你可能已经注意到 dataSources 即使我们希望其成为 context 在我们 中的 的一部分,但它并未出现在任何地方,这是因为尽管我们是在 context 外部定义的 dataSources,它已 自动包含 于每个

这是我们的 context 函数的功能:

  1. 获取 Authorization 标头(如果存在)的值,该标头包含在传入的请求中。
  2. 解码 Authorization 标头
  3. 如果解码后的值类似于电子邮件地址,请从数据库中获取该电子邮件地址的用户详细信息,并返回一个对象,其 user 中包含这些详细信息。

通过在每次context操作的开始创建一个context对象,我们的所有都可以访问已登录用户的详细信息并执行专门为用户采取的操作。

bookTripscancelTrip

现在回到resolvers.js,让我们为bookTripscancelTrip添加到Mutation对象。

server/src/resolvers.js
//Mutation: {
// login: ...
bookTrips: async (_, { launchIds }, { dataSources }) => {
const results = await dataSources.userAPI.bookTrips({ launchIds });
const launches = await dataSources.launchAPI.getLaunchesByIds({
launchIds,
});
return {
success: results && results.length === launchIds.length,
message:
results.length === launchIds.length
? 'trips booked successfully'
: `the following launches couldn't be booked: ${launchIds.filter(
id => !results.includes(id),
)}`,
launches,
};
},
cancelTrip: async (_, { launchId }, { dataSources }) => {
const result = await dataSources.userAPI.cancelTrip({ launchId });
if (!result)
return {
success: false,
message: 'failed to cancel trip',
};
const launch = await dataSources.launchAPI.getLaunchById({ launchId });
return {
success: true,
message: 'trip cancelled',
launches: [launch],
};
},

若要匹配我们的模式,这两个都返回一个对象,其符合TripUpdateResponse类型的结构。此类型的包括success指示器、状态message预订或取消的launches数组。

bookTrips 需要考虑部分成功的可能性,其中一些成功预订而另一些失败。上面的代码在message 中指示部分成功。

运行测试 mutations

我们准备测试我们的!返回Apollo Sandbox

获取登录令牌

的结构与查询完全相同,只是它们使用了 mutation 关键字。粘贴以下 并运行它:

mutation LoginUser {
login(email: "[email protected]") {
token
}
}

服务器将以如下方式做出响应

"data": {
"login": {
"token": "ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20="
}
}

token 的值是我们的登录令牌(也就是我们提供的电子邮件地址的 Base64 编码)。我们将在下一个 中使用此值。

预订旅行

让我们尝试预订一些旅行。只有经过身份验证的用户才能预订旅行,因此我们将把我们的登录令牌包含在请求中。

首先,粘贴以下 到工具的 编辑器:

mutation BookTrips {
bookTrips(launchIds: [67, 68, 69]) {
success
message
launches {
id
}
}
}

接下来,使用以下值在 标头选项卡中设置标头(在 编辑器下方面板中的一个单独文本区域)。我们提供了 标头密钥供你复制:

标头密钥
Authorization
标头值
'ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20='

运行 。你应该会看到一条成功消息,以及我们刚刚预订的旅行的 id

任务!

像这样手动运行 是测试我们的 API 的一种有用方式,但是一个真实世界的 需要额外的工具来确保它能够安全地增长和变化。在下一部分,我们将我们的服务器连接到 Apollo Studio 以激活该工具。

上一页