{"meta":{"title":"使用 GraphQL 建立调用","intro":"了解如何向 GraphQL API 验证身份，以及如何创建并运行查询和突变。","product":"GraphQL API","breadcrumbs":[{"href":"/zh/graphql","title":"GraphQL API"},{"href":"/zh/graphql/guides","title":"指南"},{"href":"/zh/graphql/guides/forming-calls-with-graphql","title":"使用 GraphQL 建立调用"}],"documentType":"article"},"body":"# 使用 GraphQL 建立调用\n\n了解如何向 GraphQL API 验证身份，以及如何创建并运行查询和突变。\n\n## 使用 GraphQL 进行身份验证\n\n您可以使用 personal access token、GitHub App 或 OAuth app\n对 GraphQL API 进行身份验证。\n\n### 使用personal access token进行身份验证\n\n若要使用 a personal access token进行身份验证，请按照 [管理个人访问令牌](/zh/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) 中的步骤进行操作。 你请求的数据将指示需要哪些范围或权限。\n\n例如，选择“issues:read”权限来读取令牌拥有访问权限的存储库中的所有问题。\n\n所有 fine-grained personal access token 均包含对公共仓库的读取权限。 若要使用 a personal access token (classic)访问公共存储库，请选择“public\\_repo”范围。\n\n如果令牌没有access资源所需的范围或权限，API 将返回一条错误消息，指出令牌所需的范围或权限。\n\n### 使用GitHub App进行身份验证\n\n如果要代表组织或其他用户使用 API， GitHub 建议使用 GitHub App。 为了使活动归属于应用，可以将应用作为应用安装进行身份验证。 为了使应用活动归属于用户，可以让应用代表用户进行身份验证。 这两种情况下都将生成一个令牌，可用于向 GraphQL API 进行身份验证。 有关详细信息，请参阅 [注册GitHub应用](/zh/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) 和 [关于使用 GitHub 应用进行身份验证](/zh/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)。\n\n### 使用OAuth app进行身份验证\n\n若要使用来自 OAuth app\n的 OAuth 令牌进行身份验证，您必须先通过 Web 应用流程或设备流程对 OAuth app\n进行授权。 然后，可以使用收到的访问令牌来访问 API。 有关详细信息，请参阅 [创建 OAuth 应用](/zh/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) 和 [授权 OAuth 应用](/zh/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps)。\n\n## GraphQL 端点\n\nREST API 具有许多终结点。 使用 GraphQL API，无论执行什么操作，终结点都保持不变。 对于GitHub.com，该终结点为：\n\n<pre>https://api.github.com/graphql</pre>\n\n## 与 GraphQL 通信\n\n由于 GraphQL作由多行 JSON 组成，因此GitHub建议使用 [GraphQL 客户端](/zh/graphql/guides/using-graphql-clients)进行 GraphQL 调用。 也可以使用 `curl` 或其他任何能够与 HTTP 进行通信的库。\n\n在 REST 中，[HTTP 谓词](/zh/rest#http-verbs)确定执行的操作。 在 GraphQL 中，无论是执行查询还是变更，都需要提供 JSON 编码的正文，因此 HTTP 动词为`POST`。 唯一的例外是[内省查询](/zh/graphql/guides/introduction-to-graphql#discovering-the-graphql-api)，它是一种简单的 `GET` 到终结点查询。 有关 GraphQL 与 REST 的详细信息，请参阅 [从 REST 迁移到 GraphQL](/zh/graphql/guides/migrating-from-rest-to-graphql)。\n\n要使用 `curl` 命令查询 GraphQL，请利用 JSON 有效负载发出 `POST` 请求。 有效负载必须包含一个名为 `query` 的字符串：\n\n```shell\ncurl -H \"Authorization: bearer TOKEN\" -X POST -d \" \\\n { \\\n   \\\"query\\\": \\\"query { viewer { login }}\\\" \\\n } \\\n\" https://api.github.com/graphql\n```\n\n> \\[!NOTE]\n> `\"query\"` 的字符串值必须进行换行符转义，否则架构将无法正确解析它。 对于 `POST` 正文，请使用外双引号和转义的内双引号。\n\n### 关于查询和突变操作\n\nGitHub GraphQL API 中允许的两种类型的操作是 *queries* 和 *mutations*。 比较 GraphQL 与 REST，查询操作就像 `GET` 请求，而突变操作则像 `POST`/`PATCH`/`DELETE`。 突变名称确定要执行的修改。\n\n有关速率限制的信息，请参阅“[GraphQL API 的速率限制和查询限制](/zh/graphql/overview/rate-limits-and-query-limits-for-the-graphql-api)”。\n\n查询和突变形式相似，但有一些重要差异。\n\n### 关于查询\n\nGraphQL 查询仅返回你指定的数据。 若要构建查询，必须指定[字段中的字段](/zh/graphql/guides/introduction-to-graphql#field)（也称为\\_嵌套子字段\\_），直到最终只返回标量。\n\n查询的结构如下：\n\n<pre>query {\n  JSON-OBJECT-TO-RETURN\n}</pre>\n\n有关实际示例，请参阅[示例查询](#example-query)。\n\n### 关于突变\n\n要建立突变，必须指定三个参数：\n\n1. 突变名称。 您要执行的修改类型。\n2. 输入对象。 要发送到服务器的数据，由输入字段组成。 将其作为参数传递到变异名称。\n3. *Payload 对象*. 您希望从服务器返回的数据由返回字段组成。 将其作为突变名称的正文传递。\n\n突变的结构如下：\n\n<pre>mutation {\n  MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {\n    MUTATION-NAME-PAYLOAD\n  }\n}</pre>\n\n此示例中的输入对象为 `MutationNameInput`，有效负载对象为 `MutationNamePayload`。\n\n在突变参考中，列出的\\_输入字段\\_是你要作为输入对象传递的内容。 列出的\\_返回字段\\_是作为有效负载对象传递的内容。\n\n有关实际示例，请参阅[示例突变](#example-mutation)。\n\n## 处理变量\n\n[变量](https://graphql.org/learn/queries/#variables)可使查询更具动态和更加强大，并且可以在传递突变输入对象时降低复杂性。\n\n下面是一个单变量查询示例：\n\n```graphql\nquery($number_of_repos:Int!) {\n  viewer {\n    name\n     repositories(last: $number_of_repos) {\n       nodes {\n         name\n       }\n     }\n   }\n}\nvariables {\n   \"number_of_repos\": 3\n}\n```\n\n使用变量包含三个步骤：\n\n1. 在 `variables` 对象中定义操作以外的变量：\n\n   ```graphql\n   variables {\n      \"number_of_repos\": 3\n   }\n   ```\n\n   此对象必须是有效的 JSON。 本示例显示了一个简单的 `Int` 变量类型，但可以定义更复杂的变量类型，如输入对象。 也可以在此定义多个变量。\n\n2. 将变量作为参数传递至操作：\n\n   ```graphql\n   query($number_of_repos:Int!){\n   ```\n\n   此参数是一个键值对，其中键为以 \\_\\_ 开头的名称（例如 `$`），值为类型（例如 `$number_of_repos`） 。 添加 `!` 以指出是否需要此类型。 如果您已经定义了多个变量，请将它们作为多个参数加入此处。\n\n3. 在操作中使用变量：\n\n   ```graphql\n   repositories(last: $number_of_repos) {\n   ```\n\n   在本示例中，我们用变量替换要检索的仓库编号。 在步骤 2 中指定类型，因为 GraphQL 会强制执行强类型化。\n\n此流程会使查询参数具有动态性。 现在，只需更改 `variables` 对象中的值，查询的其余部分则保持不变。\n\n将变量用作参数可支持动态更新 `variables` 对象中的值，而无需更改查询。\n\n## 示例查询\n\n我们来演练一个较为复杂的查询，并将此信息放在上下文中。\n\n下面的查询用于查找 `octocat/Hello-World` 存储库，查找 20 个最近关闭的问题，并返回每个问题的标题、URL 和前 5 个标签：\n\n```graphql\nquery {\n  repository(owner:\"octocat\", name:\"Hello-World\") {\n    issues(last:20, states:CLOSED) {\n      edges {\n        node {\n          title\n          url\n          labels(first:5) {\n            edges {\n              node {\n                name\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n逐行查看组成内容：\n\n* `query {`\n\n  由于我们想从服务器读取数据，而不是修改数据，因此 `query` 是根操作。 （如果未指定操作，则 `query` 也是默认操作。）\n\n* `repository(owner:\"octocat\", name:\"Hello-World\") {`\n\n  要开始查询，需找到 [`repository`](/zh/graphql/reference/repos#object-repository) 对象。 架构验证表明此对象需要 `owner` 和 `name` 参数。\n\n* `issues(last:20, states:CLOSED) {`\n\n  为考虑到存储库中的所有问题，我们调用 `issues` 对象。 （\\_可\\_查询 `issue` 中的单个 `repository`，但这需要了解我们想返回的问题编号，并将其作为参数。）\n\n  有关 `issues` 对象的一些详细信息：\n\n  * [文档](/zh/graphql/reference/repos#object-repository)中指出，此对象的类型为 `IssueConnection`。\n  * 架构验证表明此对象需要将 `last` 或 `first` 个结果作为参数，因此我们提供了 `20`。\n  * [文档](/zh/graphql/reference/repos#object-repository)也指出，此对象接受 `states` 参数，即一种 [`IssueState`](/zh/graphql/reference/issues#enum-issuestate) 枚举类型，可接受的值为 `OPEN` 或 `CLOSED`。 要仅查找已关闭的问题，可对 `states` 键赋值 `CLOSED`。\n\n* `edges {`\n\n  我们知道 `issues` 是一种连接，因为它的类型为 `IssueConnection`。 若要检索有关各个问题的数据，我们必须通过 `edges` 访问节点。\n\n* `node {`\n\n  在本示例中，我们将检索边缘末尾的节点。\n  [\n  `IssueConnection` 文档](/zh/graphql/reference/issues#object-issueconnection) 指出，`IssueConnection` 类型的末端节点是一个 `Issue` 对象。\n\n* 了解了要检索 `Issue` 对象后，现在可以查看[文档](/zh/graphql/reference/issues#object-issue)并指定要返回的字段：\n\n  ```graphql\n  title\n  url\n  labels(first:5) {\n    edges {\n      node {\n        name\n      }\n    }\n  }\n  ```\n\n  在此指定 `title` 对象的 `url`、`labels` 和 `Issue` 字段。\n\n`labels` 字段的类型为 [`LabelConnection`](/zh/graphql/reference/issues#object-labelconnection)。 与 `issues` 对象一样，`labels` 也是一种连接，因此必须将其边缘传送到连接的节点：`label` 对象。 在此节点上，可指定要返回的 `label` 对象字段，在本例中为 `name`。\n\n你可能会注意到，在 Octocat 的公共 `Hello-World` 存储库中运行此查询不会返回很多标签。 尝试在您自己的其中一个使用标签的仓库中运行，很可能会看到不同的结果。\n\n## 突变示例\n\n突变通常需要只有先执行查询才能找到的信息。 本示例显示两个操作：\n\n1. 用于获取议题 ID 的查询。\n2. 用于向议题添加表情符号反应的突变。\n\n```graphql\nquery FindIssueID {\n  repository(owner:\"octocat\", name:\"Hello-World\") {\n    issue(number:349) {\n      id\n    }\n  }\n}\n\nmutation AddReactionToIssue {\n  addReaction(input:{subjectId:\"MDU6SXNzdWUyMzEzOTE1NTE=\",content:HOORAY}) {\n    reaction {\n      content\n    }\n    subject {\n      id\n    }\n  }\n}\n```\n\n我们演练一遍这个示例。 任务听起来简单：向问题添加表情回应即可。\n\n我们怎么知道要从查询开始呢？ 还不知道。\n\n因为我们想修改服务器上的数据（向议题添加表情符号），所以先搜索架构，查找有用的突变。 参考文档所示为 [`addReaction`](/zh/graphql/reference/reactions#mutation-addreaction) 突变，其描述为：`Adds a reaction to a subject.` Perfect!\n\n突变文档列出了三个输入字段：\n\n* `clientMutationId`（`String`）\n* `subjectId`（`ID!`）\n* `content`（`ReactionContent!`）\n\n`!` 指示 `subjectId` 和 `content` 是必填字段。 必填字段 `content` 是有道理的：我们想添加反应，因此需要指定要使用哪个表情符号。\n\n但 `subjectId` 为什么是必填字段呢？ 这是因为，`subjectId` 是确定要对哪个存储库中的哪个问题做出反应的唯一方式 。\n\n因此，本示例要从查询开始：获取 `ID`。\n\n让我们逐行检查查询语句：\n\n* `query FindIssueID {`\n\n  我们将执行查询，并将其命名为 `FindIssueID`。 请注意，命名查询是可选的;我们在此处为其命名，以便我们可以将其包含在与突变相同的 GUI 客户端窗口中。\n\n* `repository(owner:\"octocat\", name:\"Hello-World\") {`\n\n  通过查询 `repository` 对象并传递 `owner` 和 `name` 参数来指定存储库。\n\n* `issue(number:349) {`\n\n  通过查询 `issue` 对象和传递 `number` 参数来指定要做出反应的问题。\n\n* `id`\n\n  我们将检索 `id` 的 `https://github.com/octocat/Hello-World/issues/349`，并作为 `subjectId` 传递。\n\n运行查询时，将获取 `id`：`MDU6SXNzdWUyMzEzOTE1NTE=`\n\n> \\[!NOTE]\n> 查询中返回的 `id` 是将在突变中作为 `subjectID` 传递的值。 文档和架构内省都不会显示这种关系；您需要理解这些名称背后的概念才能找出答案。\n\n在 ID 已知的情况下，可以继续进行突变操作：\n\n* `mutation AddReactionToIssue {`\n\n  我们将执行突变，并将其命名为 `AddReactionToIssue`。 与查询一样，命名突变是可选的;我们在此处为其命名，以便我们可以将其包含在查询所在的同一 GUI 客户端窗口中。\n\n* `addReaction(input:{subjectId:\"MDU6SXNzdWUyMzEzOTE1NTE=\",content:HOORAY}) {`\n\n  让我们来看一下这一行：\n\n  * `addReaction` 是突变的名称。\n  * `input` 是必需的参数键。 突变的参数键始终是 `input`。\n  * `{subjectId:\"MDU6SXNzdWUyMzEzOTE1NTE=\",content:HOORAY}` 是必需的参数值。 这始终是一个输入对象（因此使用花括号），由突变所需的输入字段（本例中为 `subjectId` 和 `content`）组成。\n\n  我们怎么知道该为内容使用哪个值呢？\n  [\n  `addReaction` 文档](/zh/graphql/reference/reactions#mutation-addreaction)说明 `content` 字段的类型为 [`ReactionContent`](/zh/graphql/reference/reactions#enum-reactioncontent)，这是一个枚举，因为 GitHub 问题仅支持特定表情符号反应。 这些是允许的反应值 （注意，某些值与其相应的表情符号名称不同）：\n\n  <table style=\"width:20%\">\n  <thead>\n  <tr>\n  <th scope=\"col\" style=\"text-align:left\">内容</th>\n  <th scope=\"col\" style=\"text-align:left\">表情</th>\n  </tr>\n  </thead>\n  <tbody>\n  <tr>\n  <td style=\"text-align:left\"><code>+1</code></td>\n  <td style=\"text-align:left\">👍</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>-1</code></td>\n  <td style=\"text-align:left\">👎</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>laugh</code></td>\n  <td style=\"text-align:left\">😄</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>confused</code></td>\n  <td style=\"text-align:left\">😕</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>heart</code></td>\n  <td style=\"text-align:left\">❤️</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>hooray</code></td>\n  <td style=\"text-align:left\">🎉</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>rocket</code></td>\n  <td style=\"text-align:left\">🚀</td>\n  </tr>\n  <tr>\n  <td style=\"text-align:left\"><code>eyes</code></td>\n  <td style=\"text-align:left\">👀</td>\n  </tr>\n  </tbody>\n  </table>\n\n* 调用的其余部分由负载对象构成。 我们将在此指定执行突变后由服务器返回的数据。 这几行来自 [`addReaction` 文档](/zh/graphql/reference/reactions#mutation-addreaction)，其中包含三个可能返回的字段：\n\n  * `clientMutationId`（`String`）\n  * `reaction`（`Reaction!`）\n  * `subject`（`Reactable!`）\n\n  在本示例中，返回两个必填字段（`reaction` 和 `subject`），二者均包含必填子字段（分别为 `content` 和 `id`）。\n\n我们运行突变时，响应如下：\n\n```json\n{\n  \"data\": {\n    \"addReaction\": {\n      \"reaction\": {\n        \"content\": \"HOORAY\"\n      },\n      \"subject\": {\n        \"id\": \"MDU6SXNzdWUyMTc5NTQ0OTc=\"\n      }\n    }\n  }\n}\n```\n\n就这么简单！ 将鼠标悬停在 :tada: 上找到你的用户名，查看[问题的反应](https://github.com/octocat/Hello-World/issues/349)。\n\n最后注意：当您在输入对象中传递多个字段时，语法可能会变笨拙。 将字段移入[变量](#working-with-variables)可避免这种情况。 下面是您利用变量重写原始突变的方式：\n\n```graphql\nmutation($myVar:AddReactionInput!) {\n  addReaction(input:$myVar) {\n    reaction {\n      content\n    }\n    subject {\n      id\n    }\n  }\n}\nvariables {\n  \"myVar\": {\n    \"subjectId\":\"MDU6SXNzdWUyMTc5NTQ0OTc=\",\n    \"content\":\"HOORAY\"\n  }\n}\n```\n\n> \\[!NOTE]\n> 你可能会注意到，前文示例中的 `content` 字段值（直接用于突变）在 `HOORAY` 两侧没有引号，但在变量中使用时有引号。 原因是：\n>\n> * 直接在突变中使用 `content` 时，架构预计此值的类型为 [`ReactionContent`](/zh/graphql/reference/reactions#enum-reactioncontent)，即一种枚举类型，而非字符串。 如果您在枚举值两侧添加引号，架构验证将出现错误，因为引号是为字符串保留的。\n> * 在变量中使用 `content` 时，变量部分必须为有效的 JSON，因此需要引号。 当变量在执行过程中传递至突变时，架构验证将正确解释 `ReactionContent` 类型。\n>\n> 有关枚举与字符串之间差异的更多信息，请参阅[官方 GraphQL 规格](https://spec.graphql.org/June2018/#sec-Enums)。\n\n## 其他阅读材料\n\n建立 GraphQL 调用时，可执行更\\_多\\_操作。 下面是接下来要阅读的一些内容：\n\n* [在 GraphQL API 中实现分页](/zh/graphql/guides/using-pagination-in-the-graphql-api)\n* [片段](https://graphql.org/learn/queries/#fragments)\n* [行内分段](https://graphql.org/learn/queries/#inline-fragments)\n* [指令](https://graphql.org/learn/queries/#directives)"}