# 使用 GraphQL 建立调用

了解如何向 GraphQL API 验证身份，以及如何创建并运行查询和突变。

## 使用 GraphQL 进行身份验证

您可以使用 personal access token、GitHub App 或 OAuth app
对 GraphQL API 进行身份验证。

### 使用personal access token进行身份验证

若要使用 a personal access token进行身份验证，请按照 [管理个人访问令牌](/zh/enterprise-server@3.22/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) 中的步骤进行操作。 你请求的数据将指示需要哪些范围或权限。

例如，选择“issues:read”权限来读取令牌拥有访问权限的存储库中的所有问题。

所有 fine-grained personal access token 均包含对公共仓库的读取权限。 若要使用 a personal access token (classic)访问公共存储库，请选择“public\_repo”范围。

如果令牌没有access资源所需的范围或权限，API 将返回一条错误消息，指出令牌所需的范围或权限。

### 使用GitHub App进行身份验证

如果要代表组织或其他用户使用 API， GitHub 建议使用 GitHub App。 为了使活动归属于应用，可以将应用作为应用安装进行身份验证。 为了使应用活动归属于用户，可以让应用代表用户进行身份验证。 这两种情况下都将生成一个令牌，可用于向 GraphQL API 进行身份验证。 有关详细信息，请参阅 [注册GitHub应用](/zh/enterprise-server@3.22/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) 和 [关于使用 GitHub 应用进行身份验证](/zh/enterprise-server@3.22/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)。

### 使用OAuth app进行身份验证

若要使用来自 OAuth app
的 OAuth 令牌进行身份验证，您必须先通过 Web 应用流程或设备流程对 OAuth app
进行授权。 然后，可以使用收到的访问令牌来访问 API。 有关详细信息，请参阅 [创建 OAuth 应用](/zh/enterprise-server@3.22/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) 和 [授权 OAuth 应用](/zh/enterprise-server@3.22/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps)。

## GraphQL 端点

REST API 具有许多终结点。 使用 GraphQL API，无论执行什么操作，终结点都保持不变。 对于GitHub Enterprise Server，该终结点为：

<pre>http(s)://HOSTNAME/api/graphql</pre>

## 与 GraphQL 通信

由于 GraphQL作由多行 JSON 组成，因此GitHub建议使用 [GraphQL 客户端](/zh/enterprise-server@3.22/graphql/guides/using-graphql-clients)进行 GraphQL 调用。 也可以使用 `curl` 或其他任何能够与 HTTP 进行通信的库。

在 REST 中，[HTTP 谓词](/zh/enterprise-server@3.22/rest#http-verbs)确定执行的操作。 在 GraphQL 中，无论是执行查询还是变更，都需要提供 JSON 编码的正文，因此 HTTP 动词为`POST`。 唯一的例外是[内省查询](/zh/enterprise-server@3.22/graphql/guides/introduction-to-graphql#discovering-the-graphql-api)，它是一种简单的 `GET` 到终结点查询。 有关 GraphQL 与 REST 的详细信息，请参阅 [从 REST 迁移到 GraphQL](/zh/enterprise-server@3.22/graphql/guides/migrating-from-rest-to-graphql)。

要使用 `curl` 命令查询 GraphQL，请利用 JSON 有效负载发出 `POST` 请求。 有效负载必须包含一个名为 `query` 的字符串：

```shell
curl -H "Authorization: bearer TOKEN" -X POST -d " \
 { \
   \"query\": \"query { viewer { login }}\" \
 } \
" http(s)://HOSTNAME/api/graphql
```

> \[!NOTE]
> `"query"` 的字符串值必须进行换行符转义，否则架构将无法正确解析它。 对于 `POST` 正文，请使用外双引号和转义的内双引号。

### 关于查询和突变操作

GitHub GraphQL API 中允许的两种类型的操作是 *queries* 和 *mutations*。 比较 GraphQL 与 REST，查询操作就像 `GET` 请求，而突变操作则像 `POST`/`PATCH`/`DELETE`。 突变名称确定要执行的修改。

有关速率限制的信息，请参阅“[GraphQL API 的速率限制和查询限制](/zh/enterprise-server@3.22/graphql/overview/rate-limits-and-query-limits-for-the-graphql-api)”。

查询和突变形式相似，但有一些重要差异。

### 关于查询

GraphQL 查询仅返回你指定的数据。 若要构建查询，必须指定[字段中的字段](/zh/enterprise-server@3.22/graphql/guides/introduction-to-graphql#field)（也称为\_嵌套子字段\_），直到最终只返回标量。

查询的结构如下：

<pre>query {
  JSON-OBJECT-TO-RETURN
}</pre>

有关实际示例，请参阅[示例查询](#example-query)。

### 关于突变

要建立突变，必须指定三个参数：

1. 突变名称。 您要执行的修改类型。
2. 输入对象。 要发送到服务器的数据，由输入字段组成。 将其作为参数传递到变异名称。
3. *Payload 对象*. 您希望从服务器返回的数据由返回字段组成。 将其作为突变名称的正文传递。

突变的结构如下：

<pre>mutation {
  MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {
    MUTATION-NAME-PAYLOAD
  }
}</pre>

此示例中的输入对象为 `MutationNameInput`，有效负载对象为 `MutationNamePayload`。

在突变参考中，列出的\_输入字段\_是你要作为输入对象传递的内容。 列出的\_返回字段\_是作为有效负载对象传递的内容。

有关实际示例，请参阅[示例突变](#example-mutation)。

## 处理变量

[变量](https://graphql.org/learn/queries/#variables)可使查询更具动态和更加强大，并且可以在传递突变输入对象时降低复杂性。

下面是一个单变量查询示例：

```graphql
query($number_of_repos:Int!) {
  viewer {
    name
     repositories(last: $number_of_repos) {
       nodes {
         name
       }
     }
   }
}
variables {
   "number_of_repos": 3
}
```

使用变量包含三个步骤：

1. 在 `variables` 对象中定义操作以外的变量：

   ```graphql
   variables {
      "number_of_repos": 3
   }
   ```

   此对象必须是有效的 JSON。 本示例显示了一个简单的 `Int` 变量类型，但可以定义更复杂的变量类型，如输入对象。 也可以在此定义多个变量。

2. 将变量作为参数传递至操作：

   ```graphql
   query($number_of_repos:Int!){
   ```

   此参数是一个键值对，其中键为以 \_\_ 开头的名称（例如 `$`），值为类型（例如 `$number_of_repos`） 。 添加 `!` 以指出是否需要此类型。 如果您已经定义了多个变量，请将它们作为多个参数加入此处。

3. 在操作中使用变量：

   ```graphql
   repositories(last: $number_of_repos) {
   ```

   在本示例中，我们用变量替换要检索的仓库编号。 在步骤 2 中指定类型，因为 GraphQL 会强制执行强类型化。

此流程会使查询参数具有动态性。 现在，只需更改 `variables` 对象中的值，查询的其余部分则保持不变。

将变量用作参数可支持动态更新 `variables` 对象中的值，而无需更改查询。

## 示例查询

我们来演练一个较为复杂的查询，并将此信息放在上下文中。

下面的查询用于查找 `octocat/Hello-World` 存储库，查找 20 个最近关闭的问题，并返回每个问题的标题、URL 和前 5 个标签：

```graphql
query {
  repository(owner:"octocat", name:"Hello-World") {
    issues(last:20, states:CLOSED) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}
```

逐行查看组成内容：

* `query {`

  由于我们想从服务器读取数据，而不是修改数据，因此 `query` 是根操作。 （如果未指定操作，则 `query` 也是默认操作。）

* `repository(owner:"octocat", name:"Hello-World") {`

  要开始查询，需找到 [`repository`](/zh/enterprise-server@3.22/graphql/reference/repos#object-repository) 对象。 架构验证表明此对象需要 `owner` 和 `name` 参数。

* `issues(last:20, states:CLOSED) {`

  为考虑到存储库中的所有问题，我们调用 `issues` 对象。 （\_可\_查询 `issue` 中的单个 `repository`，但这需要了解我们想返回的问题编号，并将其作为参数。）

  有关 `issues` 对象的一些详细信息：

  * [文档](/zh/enterprise-server@3.22/graphql/reference/repos#object-repository)中指出，此对象的类型为 `IssueConnection`。
  * 架构验证表明此对象需要将 `last` 或 `first` 个结果作为参数，因此我们提供了 `20`。
  * [文档](/zh/enterprise-server@3.22/graphql/reference/repos#object-repository)也指出，此对象接受 `states` 参数，即一种 [`IssueState`](/zh/enterprise-server@3.22/graphql/reference/issues#enum-issuestate) 枚举类型，可接受的值为 `OPEN` 或 `CLOSED`。 要仅查找已关闭的问题，可对 `states` 键赋值 `CLOSED`。

* `edges {`

  我们知道 `issues` 是一种连接，因为它的类型为 `IssueConnection`。 若要检索有关各个问题的数据，我们必须通过 `edges` 访问节点。

* `node {`

  在本示例中，我们将检索边缘末尾的节点。
  [
  `IssueConnection` 文档](/zh/enterprise-server@3.22/graphql/reference/issues#object-issueconnection) 指出，`IssueConnection` 类型的末端节点是一个 `Issue` 对象。

* 了解了要检索 `Issue` 对象后，现在可以查看[文档](/zh/enterprise-server@3.22/graphql/reference/issues#object-issue)并指定要返回的字段：

  ```graphql
  title
  url
  labels(first:5) {
    edges {
      node {
        name
      }
    }
  }
  ```

  在此指定 `title` 对象的 `url`、`labels` 和 `Issue` 字段。

`labels` 字段的类型为 [`LabelConnection`](/zh/enterprise-server@3.22/graphql/reference/issues#object-labelconnection)。 与 `issues` 对象一样，`labels` 也是一种连接，因此必须将其边缘传送到连接的节点：`label` 对象。 在此节点上，可指定要返回的 `label` 对象字段，在本例中为 `name`。

你可能会注意到，在 Octocat 的公共 `Hello-World` 存储库中运行此查询不会返回很多标签。 尝试在您自己的其中一个使用标签的仓库中运行，很可能会看到不同的结果。

## 突变示例

突变通常需要只有先执行查询才能找到的信息。 本示例显示两个操作：

1. 用于获取议题 ID 的查询。
2. 用于向议题添加表情符号反应的突变。

```graphql
query FindIssueID {
  repository(owner:"octocat", name:"Hello-World") {
    issue(number:349) {
      id
    }
  }
}

mutation AddReactionToIssue {
  addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}
```

我们演练一遍这个示例。 任务听起来简单：向问题添加表情回应即可。

我们怎么知道要从查询开始呢？ 还不知道。

因为我们想修改服务器上的数据（向议题添加表情符号），所以先搜索架构，查找有用的突变。 参考文档所示为 [`addReaction`](/zh/enterprise-server@3.22/graphql/reference/reactions#mutation-addreaction) 突变，其描述为：`Adds a reaction to a subject.` Perfect!

突变文档列出了三个输入字段：

* `clientMutationId`（`String`）
* `subjectId`（`ID!`）
* `content`（`ReactionContent!`）

`!` 指示 `subjectId` 和 `content` 是必填字段。 必填字段 `content` 是有道理的：我们想添加反应，因此需要指定要使用哪个表情符号。

但 `subjectId` 为什么是必填字段呢？ 这是因为，`subjectId` 是确定要对哪个存储库中的哪个问题做出反应的唯一方式 。

因此，本示例要从查询开始：获取 `ID`。

让我们逐行检查查询语句：

* `query FindIssueID {`

  我们将执行查询，并将其命名为 `FindIssueID`。 请注意，命名查询是可选的;我们在此处为其命名，以便我们可以将其包含在与突变相同的 GUI 客户端窗口中。

* `repository(owner:"octocat", name:"Hello-World") {`

  通过查询 `repository` 对象并传递 `owner` 和 `name` 参数来指定存储库。

* `issue(number:349) {`

  通过查询 `issue` 对象和传递 `number` 参数来指定要做出反应的问题。

* `id`

  我们将检索 `id` 的 `https://github.com/octocat/Hello-World/issues/349`，并作为 `subjectId` 传递。

运行查询时，将获取 `id`：`MDU6SXNzdWUyMzEzOTE1NTE=`

> \[!NOTE]
> 查询中返回的 `id` 是将在突变中作为 `subjectID` 传递的值。 文档和架构内省都不会显示这种关系；您需要理解这些名称背后的概念才能找出答案。

在 ID 已知的情况下，可以继续进行突变操作：

* `mutation AddReactionToIssue {`

  我们将执行突变，并将其命名为 `AddReactionToIssue`。 与查询一样，命名突变是可选的;我们在此处为其命名，以便我们可以将其包含在查询所在的同一 GUI 客户端窗口中。

* `addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {`

  让我们来看一下这一行：

  * `addReaction` 是突变的名称。
  * `input` 是必需的参数键。 突变的参数键始终是 `input`。
  * `{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}` 是必需的参数值。 这始终是一个输入对象（因此使用花括号），由突变所需的输入字段（本例中为 `subjectId` 和 `content`）组成。

  我们怎么知道该为内容使用哪个值呢？
  [
  `addReaction` 文档](/zh/enterprise-server@3.22/graphql/reference/reactions#mutation-addreaction)说明 `content` 字段的类型为 [`ReactionContent`](/zh/enterprise-server@3.22/graphql/reference/reactions#enum-reactioncontent)，这是一个枚举，因为 GitHub 问题仅支持特定表情符号反应。 这些是允许的反应值 （注意，某些值与其相应的表情符号名称不同）：

  <table style="width:20%">
  <thead>
  <tr>
  <th scope="col" style="text-align:left">内容</th>
  <th scope="col" style="text-align:left">表情</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td style="text-align:left"><code>+1</code></td>
  <td style="text-align:left">👍</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>-1</code></td>
  <td style="text-align:left">👎</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>laugh</code></td>
  <td style="text-align:left">😄</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>confused</code></td>
  <td style="text-align:left">😕</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>heart</code></td>
  <td style="text-align:left">❤️</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>hooray</code></td>
  <td style="text-align:left">🎉</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>rocket</code></td>
  <td style="text-align:left">🚀</td>
  </tr>
  <tr>
  <td style="text-align:left"><code>eyes</code></td>
  <td style="text-align:left">👀</td>
  </tr>
  </tbody>
  </table>

* 调用的其余部分由负载对象构成。 我们将在此指定执行突变后由服务器返回的数据。 这几行来自 [`addReaction` 文档](/zh/enterprise-server@3.22/graphql/reference/reactions#mutation-addreaction)，其中包含三个可能返回的字段：

  * `clientMutationId`（`String`）
  * `reaction`（`Reaction!`）
  * `subject`（`Reactable!`）

  在本示例中，返回两个必填字段（`reaction` 和 `subject`），二者均包含必填子字段（分别为 `content` 和 `id`）。

我们运行突变时，响应如下：

```json
{
  "data": {
    "addReaction": {
      "reaction": {
        "content": "HOORAY"
      },
      "subject": {
        "id": "MDU6SXNzdWUyMTc5NTQ0OTc="
      }
    }
  }
}
```

就这么简单！ 将鼠标悬停在 :tada: 上找到你的用户名，查看[问题的反应](https://github.com/octocat/Hello-World/issues/349)。

最后注意：当您在输入对象中传递多个字段时，语法可能会变笨拙。 将字段移入[变量](#working-with-variables)可避免这种情况。 下面是您利用变量重写原始突变的方式：

```graphql
mutation($myVar:AddReactionInput!) {
  addReaction(input:$myVar) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}
variables {
  "myVar": {
    "subjectId":"MDU6SXNzdWUyMTc5NTQ0OTc=",
    "content":"HOORAY"
  }
}
```

> \[!NOTE]
> 你可能会注意到，前文示例中的 `content` 字段值（直接用于突变）在 `HOORAY` 两侧没有引号，但在变量中使用时有引号。 原因是：
>
> * 直接在突变中使用 `content` 时，架构预计此值的类型为 [`ReactionContent`](/zh/enterprise-server@3.22/graphql/reference/reactions#enum-reactioncontent)，即一种枚举类型，而非字符串。 如果您在枚举值两侧添加引号，架构验证将出现错误，因为引号是为字符串保留的。
> * 在变量中使用 `content` 时，变量部分必须为有效的 JSON，因此需要引号。 当变量在执行过程中传递至突变时，架构验证将正确解释 `ReactionContent` 类型。
>
> 有关枚举与字符串之间差异的更多信息，请参阅[官方 GraphQL 规格](https://spec.graphql.org/June2018/#sec-Enums)。

## 其他阅读材料

建立 GraphQL 调用时，可执行更\_多\_操作。 下面是接下来要阅读的一些内容：

* [在 GraphQL API 中实现分页](/zh/enterprise-server@3.22/graphql/guides/using-pagination-in-the-graphql-api)
* [片段](https://graphql.org/learn/queries/#fragments)
* [行内分段](https://graphql.org/learn/queries/#inline-fragments)
* [指令](https://graphql.org/learn/queries/#directives)