此版本的 GitHub Enterprise 已停止服务 2021-09-23. 即使针对重大安全问题,也不会发布补丁。 要获得更好的性能、改进的安全性和新功能,请升级到 GitHub Enterprise 的最新版本。 如需升级方面的帮助,请联系 GitHub Enterprise 支持

使用 GraphQL 建立调用

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

使用 GraphQL 进行身份验证

要与 GraphQL 服务器通信,需要具有正确作用域的 OAuth 令牌。

按照“创建个人访问令牌”中的步骤创建令牌。 您需要的作用域取决于您尝试请求的数据类型。 例如,选择 User(用户)作用域以请求用户数据。 如需访问仓库信息,请选择适当的 Repository(仓库)作用域。

建议使用以下作用域:

user
public_repo
repo
repo_deployment
repo:status
read:repo_hook
read:org
read:public_key
read:gpg_key

如果资源需要特定作用域,API 会通知您。

GraphQL 端点

REST API 有多个端点;GraphQL API 只有一个端点:

http(s)://[hostname]/api/graphql

无论执行什么操作,端点都保持不变。

与 GraphQL 通信

由于 GraphQL 操作由多行 JSON 组成,因此 GitHub 建议使用 Explorer 进行 GraphQL 调用。 也可以使用 cURL 或任何其他采用 HTTP 的库。

在 REST 中,HTTP 请求方法确定执行的操作。 在 GraphQL 中,无论是执行查询还是突变,都要提供 JSON 编码的正文,因此 HTTP 请求方法是 POST。 唯一的例外是内省查询,它是一种简单的 GET 到端点查询。 有关 GraphQL 与 REST 的更多信息,请参阅“从 REST 迁移到 GraphQL”。

要使用 cURL 查询 GraphQL,请利用 JSON 有效负载提出 POST 请求。 有效负载必须包含一个名为 query 的字符串:

curl -H "Authorization: bearer token" -X POST -d " \
 { \
   \"query\": \"query { viewer { login }}\" \
 } \
" http(s)://[hostname]/api/graphql

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

关于查询和突变操作

GitHub 的 GraphQL API 中允许的两种操作类型为查询突变。 比较 GraphQL 与 REST,查询操作就像 GET 请求,而突变操作则像 POST/PATCH/DELETE突变名称确定执行哪些修改。

有关速率限制的信息,请参阅“GraphQL 资源限制”。

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

关于查询

GraphQL 查询仅返回您指定的数据。 要建立查询,必须指定字段内的字段(也称为嵌套的子字段),直到仅返回标量

查询的结构如下:

query {
  JSON objects to return
}

有关真实示例,请参阅“查询示例”。

关于突变

要建立突变,必须指定三个参数:

  1. 突变名称。 您要执行的修改类型。
  2. 输入对象。 您要发送至服务器的数据,由输入字段组成。 将其作为参数传递至突变名称。
  3. 有效负载对象。 您要从服务器返回的数据,由返回字段组成。 将其作为突变名称的正文传递。

突变的结构如下:

mutation {
  mutationName(input: {MutationNameInput!}) {
    MutationNamePayload
  }
}

本示例中的输入对象为 MutationNameInput,有效负载对象为 MutationNamePayload

在引用的突变中,列出的输入字段即是作为输入对象传递的内容。 列出的返回字段即是作为有效负载对象传递的内容。

有关真实示例,请参阅“突变示例”。

使用变量

变量可使查询更加动态和强大,并且可以在传递突变输入对象时降低复杂性。

:如果使用的是 Explorer,请确保在单独的查询变量窗格中输入变量,且 JSON 对象之前不含 variables 一词。

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

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

使用变量包含三个步骤:

  1. variables 对象中定义操作以外的变量:

    variables {
       "number_of_repos": 3
    }
    

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

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

    query($number_of_repos:Int!){
    

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

  3. 在操作中使用变量:

    repositories(last: $number_of_repos) {
    

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

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

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

查询示例

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

下面的查询用于查阅 octocat/Hello-World 仓库,查找 20 个最近关闭的议题,并返回每个议题的标题、URL 和前 5 个标签:

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 对象。 架构验证指示此对象需要 ownername 参数。

  • issues(last:20, states:CLOSED) {

    为考虑仓库中的所有议题,我们调用 issues 对象。 (我们可以查询 repository 中的单个 issue,但这需要我们了解我们想返回的议题编号,并将其作为参数。)

    关于 issues 对象的一些详细信息:

    • 文档告诉我们此对象的类型为 IssueConnection
    • 架构验证表明此对象需要将 lastfirst 个结果作为参数,因此我们提供了 20
    • 文档还告诉我们此对象接受 states 参数,即一种 IssueState 枚举类型,可接受的值为 OPENCLOSED。 要仅查找关闭状态的议题,我们对 states 键赋值 CLOSED
  • edges {

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

  • node {

    在本示例中,我们将检索边缘末尾的节点。 IssueConnection 文档指示 IssueConnection 类型末尾的节点为 Issue 对象。

  • 我们已经知道要检索 Issue 对象,现在可以查看文档并指定要返回的字段了:

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

    我们在此指定 Issue 对象的 titleurllabels 字段。

    labels 字段的类型为 LabelConnection。 与 issues 对象一样,labels 也是一种连接,因此我们必须将其边缘传送至连接的节点:label 对象。 在此节点上,我们可以指定要返回的 label 对象字段,在本例中为 name

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

突变示例

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

  1. 用于获取议题 ID 的查询。
  2. 用于向议题添加表情符号反应的突变。
query FindIssueID {
  repository(owner:"octocat", name:"Hello-World") {
    issue(number:349) {
      id
    }
  }
}

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

如果您为查询和突变命名(在本示例中为 FindIssueIDAddReactionToIssue),则可以将二者放入同一个 Explorer 窗口,但操作将作为对 GraphQL 端点的单独调用执行。 不能同时执行查询和突变,反之亦然。

我们演练一遍这个示例。 任务听起来简单:向议题添加表情符号反应即可。

那么,我们怎么知道从查询开始呢? 还不知道。

因为我们想修改服务器上的数据(向议题添加表情符号),所以先搜索架构,查找有用的突变。 参考文档所示为 addReaction 突变,其描述为:Adds a reaction to a subject. Perfect!

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

  • clientMutationId (String)
  • subjectId (ID!)
  • content (ReactionContent!)

! 表示 subjectIdcontent 为必填字段。 必填字段 content 很有意义:我们想添加反应,因此需要指定要使用哪个表情符号。

subjectId 为什么必填呢? 这是因为,subjectId 是确定要对哪个仓库中的哪个议题做出反应的唯一方式。

因此,本示例要从查询开始:获取 ID

让我们逐行检查查询:

  • query FindIssueID {

    我们将执行查询,并将其命名为 FindIssueID。 请注意,为查询命名是可选操作;我们在此为它命名,然后即可将它与突变放在同一个 Explorer 窗口中。

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

    我们通过查询 repository 对象并传递 ownername 参数来指定仓库。

  • issue(number:349) {

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

  • id

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

运行查询时,我们将得到 id: MDU6SXNzdWUyMzEzOTE1NTE=

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

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

  • mutation AddReactionToIssue {

    我们将执行突变,并将其命名为 AddReactionToIssue。 与查询一样,为突变命名是可选操作;我们在此为它命名,然后即可将它与查询放在同一个 Explorer 窗口中。

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

    让我们来检查这一行:

    • addReaction 是突变的名称。

    • input 是必需的参数键。 突变的参数键始终是 input

    • {subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY} 是必需的参数值。 突变的参数值始终是由输入字段(在本例中为 subjectIdcontent)组成的输入对象(因此带有大括号)。

      我们怎么知道内容使用哪个值呢? addReaction 文档告诉我们 content 字段的类型为 ReactionContent,即一种枚举类型,因为 GitHub 议题只支持某些表情符号反应。 这些是允许的反应值 (注意,某些值与其相应的表情符号名称不同):

      content表情符号
      +1👍
      -1👎
      微笑😄
      困惑😕
      heart❤️
      欢呼🎉
      火箭🚀
      眼睛👀
  • 调用的其余部分由有效负载对象组成。 我们将在此指定执行突变后由服务器返回的数据。 这几行来自 addReaction 文档,其中包含三个可能返回的字段:

    • clientMutationId (String)
    • reaction (Reaction!)
    • subject (Reactable!)

    在本示例中,我们返回两个必填字段(reactionsubject),二者均包含必填子字段(分别为 contentid)。

我们运行突变时,响应如下:

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

搞定! 将鼠标悬停在 🎉 上,查看您的议题反应,从而查找您的用户名。

最后注意:当您在输入对象中传递多个字段时,语法可能会变笨拙。 将字段移入变量可以避免这种情况。 下面是您利用变量重写原始突变的方式:

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

您可能会注意到,前文示例中的 content 字段值(直接用于突变)在 HOORAY 两侧没有引号,但在变量中使用时有引号。 原因是:

  • 当您直接在突变中使用 content 时,架构预计此值的类型为 ReactionContent,即一种枚举类型,而非字符串。 如果您在枚举值两侧添加引号,架构验证将出现错误,因为引号是为字符串保留的。
  • 当您在变量中使用 content 时,变量部分必须为有效的 JSON,因此需要引号。 当变量在执行过程中传递至突变时,架构验证将正确解释 ReactionContent 类型。

有关枚举类型与字符串之间差异的更多信息,请参阅官方 GraphQL 规格

延伸阅读

建立 GraphQL 调用时,您可以执行更多操作。 下面是接下来要阅读的一些内容: