使用 GraphQL 进行身份验证
您可以使用个人访问令牌、GitHub 应用或 OAuth 应用对 GraphQL API 进行身份验证。
使用个人访问令牌进行身份验证
要使用个人访问令牌进行身份验证,请遵循管理个人访问令牌中的步骤。您请求的数据将决定您需要哪些作用域或权限。
例如,选择 “issues:read” 权限,以读取令牌可访问的仓库中的所有 Issue。
所有细粒度个人访问令牌默认包含对公共仓库的读取权限。如果使用经典个人访问令牌访问公共仓库,请选择 “public_repo” 作用域。
如果您的令牌缺少访问资源所需的作用域或权限,API 将返回一条错误信息,说明令牌需要哪些作用域或权限。
使用 GitHub 应用进行身份验证
如果您想代表组织或其他用户使用 API,GitHub 推荐使用 GitHub 应用。为了将活动归属到您的应用,您可以让应用以应用安装的身份进行身份验证。为了将应用活动归属到用户,您可以让应用代表用户进行身份验证。在两种情况下,您都将生成一个令牌,可用于对 GraphQL API 进行身份验证。更多信息,请参见注册 GitHub 应用和关于使用 GitHub 应用进行身份验证。
使用 OAuth 应用进行身份验证
要使用 OAuth 应用的 OAuth 令牌进行身份验证,您必须先通过 Web 应用流程或设备流程授权您的 OAuth 应用。然后,您可以使用收到的访问令牌访问 API。更多信息,请参见创建 OAuth 应用和授权 OAuth 应用。
GraphQL 端点
REST API 有许多端点。而使用 GraphQL API 时,端点是固定的,无论执行何种操作。对于 GitHub.com,该端点为
https://api.github.com/graphql
与 GraphQL 通信
由于 GraphQL 操作是多行 JSON,GitHub 建议使用GraphQL 客户端来进行 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 }}\" \
} \
" https://api.github.com/graphql
注意
必须对 "query" 的字符串值进行换行符转义,否则模式将无法正确解析。对于 POST 请求体,请使用外层双引号并对内部双引号进行转义。
关于查询和变更操作
GitHub 的 GraphQL API 允许的两种操作类型是 查询 和 变更。相比 REST,查询类似于 GET 请求,而变更类似于 POST、PATCH 或 DELETE。变更名称决定将执行哪种修改。
有关速率限制的信息,请参见GraphQL API 的速率限制和查询限制。
查询和变更在形式上大体相同,但存在一些重要差异。
关于查询
GraphQL 查询仅返回您指定的数据。构造查询时,必须在 字段内部再指定字段(即 嵌套子字段),直至只返回 标量。
查询的结构如下
query {
JSON-OBJECT-TO-RETURN
}
实际示例请参见示例查询。
关于变更
要构造一个变更,需要指定三件事
- 变更名称。您想执行的修改类型。
- 输入对象。要发送到服务器的数据,由 输入字段 组成。将其作为对变更名称的参数传入。
- 负载对象。希望服务器返回的数据,由 返回字段 组成。将其作为变更名称的主体传入。
变更的结构如下
mutation {
MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {
MUTATION-NAME-PAYLOAD
}
}
本示例中的输入对象是 MutationNameInput,负载对象是 MutationNamePayload。
在变更参考文档中,列出的 输入字段 即为您在输入对象中传递的字段;列出的 返回字段 则为您在负载对象中指定的字段。
实际示例请参见示例变更。
使用变量
变量可以让查询更具动态性和强大性,并且在传递变更的输入对象时可以降低复杂度。
以下是带单个变量的示例查询
query($number_of_repos:Int!) {
viewer {
name
repositories(last: $number_of_repos) {
nodes {
name
}
}
}
}
variables {
"number_of_repos": 3
}
使用变量有三个步骤
-
在操作外部的
variables对象中定义变量variables { "number_of_repos": 3 }该对象必须是有效的 JSON。本示例展示了一个简单的
Int变量类型,但也可以定义更复杂的变量类型,例如输入对象。这里也可以一次定义多个变量。 -
将变量作为参数传递给操作
query($number_of_repos:Int!){参数是键值对,键是以
$开头的 名称(例如$number_of_repos),值是 类型(例如Int)。在类型后加!表示该类型为必需。如果定义了多个变量,则在此处以多个参数的形式一起传入。 -
在操作内部使用变量
repositories(last: $number_of_repos) {在本例中,我们将变量用于要检索的仓库数量。第 2 步中需要指定类型,因为 GraphQL 强制执行强类型检查。
此过程使查询参数变得动态。我们只需修改 variables 对象中的值,查询本体保持不变。
将变量用作参数可以在不更改查询本身的情况下动态更新 variables 对象中的值。
示例查询
让我们逐步浏览一个更复杂的查询,并将这些信息放在上下文中。
以下查询会查找 octocat/Hello-World 仓库,获取最近的 20 条已关闭 Issue,并返回每个 Issue 的标题、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对象。模式验证表明此对象需要owner和name参数。 -
issues(last:20, states:CLOSED) {为了遍历仓库中的所有 Issue,我们调用
issues对象。(我们可以在repository上查询单个issue,但那样需要知道要返回的 Issue 编号并将其作为参数提供。)关于
issues对象的一些细节- 文档告诉我们该对象的类型为
IssueConnection。 - 模式验证表明此对象需要
last或first参数来指定返回的结果数量,所以我们提供20。 - 文档还说明该对象接受
states参数,它是一个IssueState枚举,取值可以是OPEN或CLOSED。为了只获取已关闭的 Issue,我们将states设置为CLOSED。
- 文档告诉我们该对象的类型为
-
edges {因为
issues是一个连接(Connection),其类型为IssueConnection,要获取单个 Issue 的数据,需要通过edges访问节点。 -
node {这里我们取出 edge 末端的节点。
IssueConnection文档指出,IssueConnection的节点是一个Issue对象。 -
既然我们知道要检索的是
Issue对象,可以查看文档,并指定想返回的字段。title url labels(first:5) { edges { node { name } } }此处我们指定了
title、url和labels三个字段。labels字段的类型为LabelConnection。和issues类似,labels也是一个连接,需要通过其edges访问连接的节点——即label对象。在节点上,我们可以指定要返回的label字段,本例中为name。
您可能注意到,在 Octocat 的公开 Hello-World 仓库上运行此查询时,返回的标签并不多。请尝试在您自己的一个使用了标签的仓库中运行,您会看到不同的结果。
示例变更
变更往往需要先通过查询获取信息后才能执行。本示例展示了两个操作:
- 查询以获取 Issue ID。
- 变更以向该 Issue 添加表情符号响应。
query FindIssueID {
repository(owner:"octocat", name:"Hello-World") {
issue(number:349) {
id
}
}
}
mutation AddReactionToIssue {
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
reaction {
content
}
subject {
id
}
}
}
让我们逐步浏览示例。任务看起来很简单:向 Issue 添加表情符号响应。
那么我们为何先要进行查询呢?目前尚未知道。
因为我们需要在服务器上修改数据(给 Issue 添加表情),所以首先在模式中搜索合适的变更。参考文档显示有 addReaction 变更,描述为:Adds a reaction to a subject. 完美!
该变更的文档列出了三个输入字段:
clientMutationId(String)subjectId(ID!)content(ReactionContent!)
! 表示 subjectId 和 content 为必填字段。content 必填是合理的,因为我们需要指定要使用的表情符号。
为什么 subjectId 必填?因为它是唯一能够标识 *哪个* 仓库中的 *哪个* Issue 的方式。
这就是我们先使用查询的原因:获取 ID。
让我们逐行检查查询:
-
query FindIssueID {这里我们执行查询,并将其命名为
FindIssueID。请注意,为查询命名是可选的;我们在此命名是为了能够在同一 GUI 客户端窗口中同时显示查询和变更。 -
repository(owner:"octocat", name:"Hello-World") {我们通过查询
repository对象并传入owner与name参数来指定仓库。 -
issue(number:349) {我们通过查询
issue对象并传入number参数来指定要响应的 Issue。 -
id这里我们获取
https://github.com/octocat/Hello-World/issues/349的id,随后将其作为subjectId传入。
运行查询后得到的 id 为:MDU6SXNzdWUyMzEzOTE1NTE=
注意
查询返回的 id 将在变更中作为 subjectID 传入。文档或模式自省都不会直接说明这种关联;您需要理解名称背后的概念才能搞清楚。
已知 ID 后,我们即可继续执行变更。
-
mutation AddReactionToIssue {这里我们执行变更,并将其命名为
AddReactionToIssue。和查询一样,为变更命名是可选的;我们在此命名是为了能够在同一 GUI 客户端窗口中同时显示查询和变更。 -
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {让我们检查这一行:
addReaction是该变更的名称。input是必需的参数键。对于所有变更,这个键始终为input。{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}是必需的参数值。它始终是一个输入对象(因此使用大括号),由输入字段(本例中的subjectId与content)组成。
我们如何知道
content应该填什么值?addReaction文档告诉我们,content字段的类型是ReactionContent,这是一个枚举,因为 GitHub Issue 只支持特定的表情符号。以下是支持的取值(注意某些值与对应的表情名称不同):内容 表情符号 +1👍 -1👎 laugh😄 confused😕 heart❤️ hooray🎉 rocket🚀 eyes👀 -
剩余部分由负载对象组成。这是我们在执行变更后希望服务器返回的数据。以下行摘自
addReaction文档,列出了三种可能的返回字段:clientMutationId(String)reaction(Reaction!)subject(Reactable!)
本示例我们返回了两个必需字段(
reaction与subject),它们各自还有必需的子字段(分别为content与id)。
运行变更后得到的响应如下:
{
"data": {
"addReaction": {
"reaction": {
"content": "HOORAY"
},
"subject": {
"id": "MDU6SXNzdWUyMTc5NTQ0OTc="
}
}
}
}
就这样!请前往 该 Issue 的响应页面,将鼠标悬停在 🎉 上即可看到您的用户名。
最后提示:当在输入对象中传入多个字段时,语法可能会变得繁琐。将这些字段移入变量可以简化。下面演示如何使用变量重写上述变更。
mutation($myVar:AddReactionInput!) {
addReaction(input:$myVar) {
reaction {
content
}
subject {
id
}
}
}
variables {
"myVar": {
"subjectId":"MDU6SXNzdWUyMTc5NTQ0OTc=",
"content":"HOORAY"
}
}
注意
您可能注意到,在前面的示例中直接在变更里使用的 content 字段值 **HOORAY** 没有加引号,而在变量里使用时则加了引号。这是有原因的。
- 直接在变更中使用
content时,模式期望该值的类型为ReactionContent,它是一个枚举而不是字符串。如果在枚举值周围加引号,模式验证会报错,因为引号只用于字符串。 - 在变量中使用
content时,变量段必须是有效的 JSON,因此必须加引号。执行时,模式会正确把 JSON 中的字符串解释为ReactionContent枚举类型。
欲了解枚举与字符串之间的区别,请参见官方 GraphQL 规范。
延伸阅读
在构造 GraphQL 调用时,还有大量可做的事,下面列出了一些后续可探索的方向: