使用 GraphQL 进行身份验证
可以使用 personal access token、GitHub App 或 OAuth app 向 GraphQL API 进行身份验证。
使用 personal access token
进行身份验证
若要使用 personal access token 进行身份验证,请按照“管理个人访问令牌”中的步骤操作。 你请求的数据将指示需要哪些范围或权限。
例如,选择“issues:read”权限以读取令牌有权访问的存储库中的所有问题。
所有 fine-grained personal access token 都包括对公共存储库的读取访问权限。 若要访问具有 personal access token (classic) 的公共存储库,请选择“public_repo”范围。
如果令牌没有访问资源所需的范围或权限,API 将返回一条错误消息,指出令牌所需的范围或权限。
使用 GitHub App
进行身份验证
如果要代表组织或其他用户使用 API,GitHub 建议使用 GitHub App。 为了使活动归属于应用,可以将应用作为应用安装进行身份验证。 为了使应用活动归属于用户,可以让应用代表用户进行身份验证。 这两种情况下都将生成一个令牌,可用于向 GraphQL API 进行身份验证。 有关详细信息,请参阅“注册 GitHub 应用”和“关于使用 GitHub 应用进行身份验证”。
使用 OAuth app 进行身份验证
若要使用来自 OAuth app 的 OAuth 令牌进行身份验证,必须先使用 Web 应用程序流或设备流授权 OAuth app。 然后,可以使用收到的访问令牌来访问 API。 有关详细信息,请参阅“创建 OAuth 应用”和“授权 OAuth 应用”。
GraphQL 端点
REST API 有多个端点;GraphQL API 只有一个端点:
https://api.github.com/graphql
无论执行什么操作,端点都保持不变。
与 GraphQL 通信
由于 GraphQL 操作由多行 JSON 组成,因此 GitHub 建议使用浏览器进行 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 中允许的两种操作类型为查询和突变 。 比较 GraphQL 与 REST,查询操作就像 GET
请求,而突变操作则像 POST
/PATCH
/DELETE
。 突变名称确定执行的修改内容。
有关速率限制的信息,请参阅“Rate limits and node limits for the GraphQL API”。
查询和突变形式相似,但有一些重要差异。
关于查询
GraphQL 查询仅返回你指定的数据。 要建立查询,必须指定字段内的字段(也称为嵌套的子字段),直到仅返回标量。
查询的结构如下:
query { JSON-OBJECT-TO-RETURN }
有关实际示例,请参阅“示例查询”。
关于突变
要建立突变,必须指定三个参数:
- 突变名称。 您要执行的修改类型。
- 输入对象。 要发送到服务器的数据,由输入字段组成。 将其作为参数传递至突变名称。
- 有效负载对象。 要从服务器返回的数据,由返回字段组成。 将其作为突变名称的正文传递。
突变的结构如下:
mutation { MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) { MUTATION-NAME-PAYLOAD } }
此示例中的输入对象为 MutationNameInput
,有效负载对象为 MutationNamePayload
。
在突变引用中,列出的输入字段是作为输入对象传递的内容。 列出的返回字段是作为有效负载对象传递的内容。
有关实际示例,请参阅“示例突变”。
使用变量
变量可使查询更具动态和更加强大,并且可以在传递突变输入对象时降低复杂性。
注意:如果使用的是此浏览器,请确保在单独的“查询变量”窗格中输入变量,且 JSON 对象之前不含 variables
一词。
下面是一个单变量查询示例:
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 个最近关闭的问题,并返回每个问题的标题、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) {
为考虑到存储库中的所有问题,我们调用
issues
对象。 (可查询repository
中的单个issue
,但这需要了解我们想返回的问题编号,并将其作为参数。)有关
issues
对象的一些详细信息:- 文档中指出,此对象的类型为
IssueConnection
。 - 架构验证表明此对象需要将
last
或first
个结果作为参数,因此我们提供了20
。 - 文档也指出,此对象接受
states
参数,即一种IssueState
枚举类型,可接受的值为OPEN
或CLOSED
。 要仅查找已关闭的问题,可对states
键赋值CLOSED
。
- 文档中指出,此对象的类型为
-
edges {
我们知道
issues
是一种连接,因为它的类型为IssueConnection
。 要检索关于各个问题的数据,必须通过edges
访问节点。 -
node {
在本示例中,我们将检索边缘末尾的节点。
IssueConnection
文档指示IssueConnection
类型末尾的节点为Issue
对象。 -
了解了要检索
Issue
对象后,现在可以查看文档并指定要返回的字段:title url labels(first:5) { edges { node { name } } }
在此指定
Issue
对象的title
、url
和labels
字段。labels
字段的类型为LabelConnection
。 与issues
对象一样,labels
也是一种连接,因此必须将其边缘传送到连接的节点:label
对象。 在此节点上,可指定要返回的label
对象字段,在本例中为name
。
你可能会注意到,在 Octocat 的公共 Hello-World
存储库中运行此查询不会返回很多标签。 尝试在您自己的其中一个使用标签的仓库中运行,很可能会看到不同的结果。
突变示例
突变通常需要只有先执行查询才能找到的信息。 本示例显示两个操作:
- 用于获取议题 ID 的查询。
- 用于向议题添加表情符号反应的突变。
query FindIssueID {
repository(owner:"octocat", name:"Hello-World") {
issue(number:349) {
id
}
}
}
mutation AddReactionToIssue {
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
reaction {
content
}
subject {
id
}
}
}
如果为查询和突变命名(在本示例中为 FindIssueID
和 AddReactionToIssue
),则可以将二者放入此浏览器的同一个窗口,但操作将作为对 GraphQL 终结点的单独调用执行。 不能同时执行查询和突变,反之亦然。
我们演练一遍这个示例。 任务听起来简单:向议题添加表情符号反应即可。
那么,我们怎么知道从查询开始呢? 还不知道。
因为我们想修改服务器上的数据(向议题添加表情符号),所以先搜索架构,查找有用的突变。 参考文档所示为 addReaction
突变,其描述为:Adds a reaction to a subject.
Perfect!
突变文档列出了三个输入字段:
clientMutationId
(String
)subjectId
(ID!
)content
(ReactionContent!
)
!
指示 subjectId
和 content
是必填字段。 必填字段 content
是有道理的:我们想添加反应,因此需要指定要使用哪个表情符号。
但 subjectId
为什么是必填字段呢? 这是因为,subjectId
是确定要对哪个存储库中的哪个问题做出反应的唯一方式 。
因此,本示例要从查询开始:获取 ID
。
让我们逐行检查查询:
-
query FindIssueID {
我们将执行查询,并将其命名为
FindIssueID
。 请注意,为查询命名是可选操作;我们在此为它命名,然后即可将它与突变放在同一个 Explorer 窗口中。 -
repository(owner:"octocat", name:"Hello-World") {
通过查询
repository
对象并传递owner
和name
参数来指定存储库。 -
issue(number:349) {
通过查询
issue
对象和传递number
参数来指定要做出反应的问题。 -
id
我们将检索
https://github.com/octocat/Hello-World/issues/349
的id
,并作为subjectId
传递。
运行查询时,将获取 id
:MDU6SXNzdWUyMzEzOTE1NTE=
注意:查询中返回的 id
是将在突变中作为 subjectID
传递的值。 文档和架构内省都不会显示这种关系;您需要理解这些名称背后的概念才能找出答案。
在 ID 已知的情况下,可以继续进行突变操作:
-
mutation AddReactionToIssue {
我们将执行突变,并将其命名为
AddReactionToIssue
。 与查询一样,为突变命名是可选操作;我们在此为它命名,然后即可将它与查询放在同一个 Explorer 窗口中。 -
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
让我们来检查这一行:
addReaction
是突变的名称。input
是必需的参数键。 突变的参数键始终是input
。{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}
是必需的参数值。 突变的参数值始终是由输入字段(在本例中为subjectId
和content
)组成的输入对象(因此带有大括号)。
我们怎么知道内容使用哪个值呢?
addReaction
文档指出,content
字段的类型为ReactionContent
,即一种枚举类型,因为 GitHub 只支持使用某些表情符号进行反应。 这些是允许的反应值 (注意,某些值与其相应的表情符号名称不同):内容 表情 +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="
}
}
}
}
就这么简单! 将鼠标悬停在 ???? 上找到你的用户名,查看问题的反应。
最后注意:当您在输入对象中传递多个字段时,语法可能会变笨拙。 将字段移入变量可避免这种情况。 下面是您利用变量重写原始突变的方式:
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 调用时,可执行更多操作。 下面是接下来要阅读的一些内容: