0%

Apollo入门引导(四):编写变更解析器

接上篇 —— Apollo 入门引导(三):编写查询解析器 —— 继续翻译 Apollo 的官网入门引导

学习 GraphQL 的变更是如何修改数据的。

Apollo 入门引导 - 目录:

  1. 介绍
  2. 构建 schema
  3. 连接数据源
  4. 编写查询解析器
  5. 编写变更解析器
  6. 连接 Apollo Studio
  7. 创建 Apollo 客户端
  8. 通过查询获取数据
  9. 通过变更修改数据
  10. 管理本地状态

完成时间:10 分钟

上一篇已经编写了 schema 字段中查询操作所需的所有解析器。现在继续为 schema 的变更编写解析器。这个过程基本是相同的。

登录

首先为 Mutation.login 编写一个解析器,使用户可以登录到应用程序。将以下内容添加到解析器映射中的 Query 字段下面:

1
2
3
4
5
6
7
8
9
// Query: {
// ...
// },
Mutation: {
login: async (_, { email }, { dataSources }) => {
const user = await dataSources.userAPI.findOrCreateUser({ email });
if (user) return Buffer.from(email).toString('base64');
}
},

该解析器获取一个 email 地址,并返回对应用户实体的登录 token。如果该 email 地址不存在对应的用户实体,则会创建一个。

在下一章中将学习如何在客户端上保存此登录 token。

验证登录用户

示例应用中使用的身份验证方法不是完全安全的,生产环境中不应使用。但是可以将以下示例的原理应用于到 安全 的基于 token 的身份验证方法中。

Mutation.login 解析器返回一个 token,客户端可以在服务中使用该 token 进行身份验证。现在需要向服务添加用于身份验证逻辑。

src/index.js 中,导入 isEmail 函数,并将 context 函数传给 ApolloServer 的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const isEmail = require('isemail');

const server = new ApolloServer({
context: async ({ req }) => {
// 每次请求时的简单身份验证
const auth = (req.headers && req.headers.authorization) || '';
const email = Buffer.from(auth, 'base64').toString('ascii');

if (!isEmail.validate(email)) return { user: null };

// email通过email查找用户
const users = await store.users.findOrCreate({ where: { email } });
const user = (users && users[0]) || null;

return { user: { ...user.dataValues } };
},
// 可选的构造函数选项
});

客户端发送到我们服务的每一次 GraphQL 操作,都会被调用一次上面定义的 context 函数。该函数的返回值成为 context 参数,该参数传给和对应操作一起运行的每个解析器。

下面是 context 函数的作用:

  1. 获取传入请求中包含的 Authorization 请求头(如果有)的值。
  2. 解码 Authorization 请求头的值。
  3. 如果解码后的值是 email 地址,则从数据库中获取该电子邮件地址对应用户详细信息,并在 user 字段中返回包含这些详细信息的对象。

通过在每个操作执行开始时创建此 context 对象,所有的解析器都可以访问已登录用户的详细信息,并针对该用户执行特定的操作。

bookTripscancelTrip

下面将 bookTripscancelTrip 的解析器添加到 Mutation 对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//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
? '成功预定旅程'
: `下面的发射不能被预定: ${launchIds.filter(
id => !results.includes(id),
)}`,
launches,
};
},
cancelTrip: async (_, { launchId }, { dataSources }) => {
const result = await dataSources.userAPI.cancelTrip({ launchId });

if (!result)
return {
success: false,
message: '取消旅程失败',
};

const launch = await dataSources.launchAPI.getLaunchById({ launchId });
return {
success: true,
message: '旅程已取消',
launches: [launch],
};
},

为了匹配 schema,这两个解析器都返回一个符合 TripUpdateResponse 类型结构的对象。此类型的字段包括 success 指示器,状态 message 和预定或者取消变更执行后的 launches 数组。

bookTrips 解析器需要考虑部分成功(partial success)的可能性,其中某些发射被成功预订,而另一些失败了。。上面 message 字段里的代码表示部分成功。

测试变更

现在已经准备好测试我们的变更!重新启动服务,然后在浏览器中打开到 Apollo Studio Explorer 或 GraphQL Playground。

获取登录 token

GraphQL 变更的结构与查询完全相同,只不过使用的是 mutation 关键字。粘贴下面变更的代码并运行:

1
2
3
mutation LoginUser {
login(email: "daisy@apollographql.com")
}

服务返回的响应如下:

1
2
3
"data": {
"login": "ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20="
}

下面的字符串是登录的 token(仅仅只是提供的电子邮件地址的 Base64 编码)。复制它给下一个变更操作使用。

1
ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20=

预定行程

现在来尝试预订一些旅行。因为仅允许通过身份验证的用户预订行程,所以将在请求中包括登录 token。

首先,将下面的变更粘贴到工具的查询编辑器中:

1
2
3
4
5
6
7
8
9
mutation BookTrips {
bookTrips(launchIds: [67, 68, 69]) {
success
message
launches {
id
}
}
}

接下来,将以下内容粘贴到工具的 Headers 面板中:

1
2
3
{
"authorization": "ZGFpc3lAYXBvbGxvZ3JhcGhxbC5jb20="
}

运行变更,应该会看到一条成功 message,以及刚刚预订的旅程的 id 数组。

在 GraphQL Playground 中手动运行变更是测试 API 的很有帮助的方式,但是真实的应用需要其他工具来确保其数据图安全地添加和更改。在下一部分中,我们将服务连接到 Apollo Studio 工具。


前端记事本,不定期更新,欢迎关注!


👆 全文结束,棒槌时间到 👇