{"meta":{"title":"使用 REST API 和 Ruby 编写脚本","intro":"了解如何使用 Octokit.rb SDK 编写脚本，以便与 REST API 交互。","product":"REST API","breadcrumbs":[{"href":"/zh/enterprise-cloud@latest/rest","title":"REST API"},{"href":"/zh/enterprise-cloud@latest/rest/guides","title":"指南"},{"href":"/zh/enterprise-cloud@latest/rest/guides/scripting-with-the-rest-api-and-ruby","title":"使用 Ruby 编写脚本"}],"documentType":"article"},"body":"# 使用 REST API 和 Ruby 编写脚本\n\n了解如何使用 Octokit.rb SDK 编写脚本，以便与 REST API 交互。\n\n## 关于 Octokit.rb\n\n如果想使用 Ruby 编写一个脚本来与 GitHub REST API 进行交互，GitHub 建议使用 Octokit.rb SDK。 Octokit.rb 由 GitHub 维护。 SDK 实现了最佳做法，可让你更轻松地通过 Ruby 与 REST API 进行交互。 Octokit.rb 适用于所有现代浏览器、Node.rb 和 Deno。 有关 Octokit.rb 的详细信息，请参阅 [Octokit.rb 自述文件](https://github.com/octokit/octokit.rb/#readme)。\n\n## 先决条件\n\n本指南假定你熟悉 Ruby 和 GitHub REST API。 有关 REST API 的详细信息，请参阅 [REST API 入门](/zh/enterprise-cloud@latest/rest/guides/getting-started-with-the-rest-api)。\n\n必须安装和导入 `octokit` gem，才能使用 Octokit.rb 库。 本指南使用符合 Ruby 约定的导入语句。 有关不同安装方法的详细信息，请参阅 [Octokit.rb 自述文件的安装部分](https://github.com/octokit/octokit.rb/#installation)。\n\n## 实例化和身份验证\n\n> \\[!WARNING]\n> 将身份验证凭据视为密码。\n>\n> 要确保凭据安全，可以将凭据存储为机密，并通过 GitHub Actions 运行脚本。 有关详细信息，请参阅“[在 GitHub Actions 中使用机密](/zh/enterprise-cloud@latest/actions/security-guides/encrypted-secrets)”。\n\n> 还可以将凭据存储为 Codespaces 机密，并在 Codespaces 中运行脚本。 有关详细信息，请参阅“[管理 GitHub Codespaces 的账户专属的机密](/zh/enterprise-cloud@latest/codespaces/managing-your-codespaces/managing-encrypted-secrets-for-your-codespaces)”。\n\n> 如果无法使用这些选项，请考虑使用其他 CLI 服务来安全地存储凭据。\n\n### 使用 personal access token 进行身份验证\n\n如果要将 GitHub REST API 用于个人用途，可以创建 personal access token。 有关创建 personal access token 的详细信息，请参阅 [管理个人访问令牌](/zh/enterprise-cloud@latest/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)。\n\n首先，需要 `octokit` 库。 然后，通过将 personal access token 作为 `Octokit` 选项传递来创建 `access_token` 的实例。 在以下示例中，将 `YOUR-TOKEN` 替换为 personal access token。\n\n```ruby copy\nrequire 'octokit'\n\noctokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')\n```\n\n### 使用 GitHub App 进行身份验证\n\n如果要代表组织或其他用户使用 API，GitHub 建议使用 GitHub App。 如果某个端点对 GitHub Apps 可用，那么该端点的 REST 参考文档将指示需要哪种类型的 GitHub App 令牌。 有关详细信息，请参阅 [注册GitHub应用](/zh/enterprise-cloud@latest/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app) 和 [关于使用 GitHub 应用进行身份验证](/zh/enterprise-cloud@latest/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)。\n\n通过将 GitHub App 的信息作为选项传递，而不需要 `octokit` 即可创建 `Octokit::Client` 的实例。 在以下示例中，将 `APP_ID` 替换为应用的 ID，将 `PRIVATE_KEY` 替换为应用的私钥，将 `INSTALLATION_ID` 替换为要代表其进行身份验证的应用的安装 ID。 可以在应用的设置页面上找到应用的 ID 并生成私钥。 有关详细信息，请参阅“[管理GitHub应用的私钥](/zh/enterprise-cloud@latest/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps)”。 可以使用 `GET /users/{username}/installation`、`GET /repos/{owner}/{repo}/installation` 或 `GET /orgs/{org}/installation` 终结点获取安装 ID。 有关详细信息，请参阅 [GitHub Apps 的 REST API 终结点](/zh/enterprise-cloud@latest/rest/apps/apps)。\n\n```ruby copy\nrequire 'octokit'\n\napp = Octokit::Client.new(\n  client_id: APP_ID,\n  client_secret: PRIVATE_KEY,\n  installation_id: INSTALLATION_ID\n)\n\noctokit = Octokit::Client.new(bearer_token: app.create_app_installation.access_token)\n```\n\n### 在 GitHub Actions 中进行身份验证\n\n如果要在 GitHub Actions 工作流中使用 API，则 GitHub 建议使用内置 `GITHUB_TOKEN` 进行身份验证，而不是创建令牌。 可以使用 `GITHUB_TOKEN` 密钥向 `permissions` 授予权限。 有关 `GITHUB_TOKEN` 的详细信息，请参阅 [GITHUB\\_TOKEN](/zh/enterprise-cloud@latest/actions/concepts/security/github_token)。\n\n如果工作流需要访问工作流存储库之外的资源，则无法使用 `GITHUB_TOKEN`。 在这种情况下，请将凭据存储为机密，并将以下示例中的 `GITHUB_TOKEN` 替换为机密的名称。 有关机密的详细信息，请参阅 [在 GitHub Actions 中使用机密](/zh/enterprise-cloud@latest/actions/security-guides/using-secrets-in-github-actions)。\n\n如果使用 `run` 关键字在 GitHub Actions 工作流中执行 Ruby 脚本，则可以将 `GITHUB_TOKEN` 的值存储为环境变量。 脚本可以作为 `ENV['VARIABLE_NAME']` 访问环境变量。\n\n例如，此工作流步骤将 `GITHUB_TOKEN` 存储在名为 `TOKEN` 的环境变量中：\n\n```yaml\n- name: Run script\n  env:\n    TOKEN: ${{ secrets.GITHUB_TOKEN }}\n  run: |\n    ruby .github/actions-scripts/use-the-api.rb\n```\n\n工作流运行的脚本使用 `ENV['TOKEN']` 进行身份验证：\n\n```ruby copy\nrequire 'octokit'\n\noctokit = Octokit::Client.new(access_token: ENV['TOKEN'])\n```\n\n### 无需身份验证即可实例化\n\n可以在不进行身份验证的情况下使用 REST API，但速率限制较低，并且无法使用某些终结点。 要在不进行身份验证的情况下创建 `Octokit` 的实例，请不要传递 `access_token` 选项。\n\n```ruby copy\nrequire 'octokit'\n\noctokit = Octokit::Client.new\n```\n\n## 发出请求\n\nOctokit 支持多种请求方式。 如果知道终结点的 HTTP 谓词和路径，则可以使用 `request` 方法发出请求。 如果要利用 IDE 中的自动完成和键入功能，可以使用 `rest` 方法。 对于分页终结点，可以使用 `paginate` 方法来请求多页数据。\n\n### 使用 `request` 方法发出请求\n\n若要使用 `request` 方法发出请求，请将 HTTP 方法和路径作为第一个参数传递。 将哈希中的任何主体、查询或路径参数作为第二个自变量传递。 例如，向 `GET` 发出 `/repos/{owner}/{repo}/issues` 请求并传递 `owner`、`repo` 和 `per_page` 参数：\n\n```ruby copy\noctokit.request(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 2)\n```\n\n```\n          `request` 方法会自动传递 `Accept: application/vnd.github+json` 标头。 要传递其他标头或不同的 `Accept` 标头，请将 `headers` 选项添加到作为第二个自变量传递的哈希。 \n          `headers` 选项的值是一个哈希，其中标头名称作为键，标头值作为值。 例如，若要发送具有 `content-type` 值的 `text/plain` 标头：\n```\n\n```ruby copy\noctokit.request(\"POST /markdown/raw\", text: \"Hello **world**\", headers: { \"content-type\" => \"text/plain\" })\n```\n\n### 使用 `rest` 接口方法发出请求\n\n每个 REST API 终结点在 Octokit 中都有一个关联的 `rest` 终结点方法。 为方便起见，这些方法通常会在 IDE 中自动完成。 可以将任何参数作为哈希传递给该方法。\n\n```ruby copy\noctokit.rest.issues.list_for_repo(owner: \"github\", repo: \"docs\", per_page: 2)\n```\n\n### 发出分页请求\n\n如果终结点已分页，并且你想要提取多页结果，则可以使用 `paginate` 方法。\n`paginate` 将依次提取结果的下一页，直至到达最后一页，然后将所有结果作为数组返回。 一些端点将分页结果以对象中的数组返回，而不是以数组的形式返回分页结果。\n`paginate` 始终返回项的数组，即使原始结果为一个对象。\n\n例如，以下示例从 `github/docs` 存储库中获取所有问题。 虽然它一次请求 100 个问题，但函数在到达最后一页数据之前不会返回。\n\n```ruby copy\nissue_data = octokit.paginate(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 100)\n```\n\n```\n          `paginate` 方法接受可选块，可用于处理每个结果页。 这样，你只能从响应中收集所需的数据。 例如，以下示例继续提取结果，直到返回标题中包含“test”的问题。 对于返回的数据页面，仅存储问题标题和作者。\n```\n\n```ruby copy\nissue_data = octokit.paginate(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 100) do |response, done|\n  response.data.map do |issue|\n    if issue.title.include?(\"test\")\n      done.call\n    end\n    { title: issue.title, author: issue.user.login }\n  end\nend\n```\n\n可以使用 `octokit.paginate.iterator()` 一次循环访问一个页面，而不是一次提取所有结果。 例如，以下示例一次提取一页结果，并在提取下一页之前处理页面中的每个对象。 到达标题中包含“test”的问题后，脚本将停止迭代，并返回已处理的每个对象的问题标题和问题作者。 迭代器是提取分页数据的内存效率最高的方法。\n\n```ruby copy\niterator = octokit.paginate.iterator(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 100)\nissue_data = []\nbreak_loop = false\niterator.each do |data|\n  break if break_loop\n  data.each do |issue|\n    if issue.title.include?(\"test\")\n      break_loop = true\n      break\n    else\n      issue_data << { title: issue.title, author: issue.user.login }\n    end\n  end\nend\n```\n\n也可以将 `paginate` 方法与 `rest` 终结点方法一起使用。 将 `rest` 端点方法作为第一个自变量传递，并将任何参数作为第二个自变量传递。\n\n```ruby copy\niterator = octokit.paginate.iterator(octokit.rest.issues.list_for_repo, owner: \"github\", repo: \"docs\", per_page: 100)\n```\n\n有关分页的详细信息，请参阅 [在 REST API 中使用分页](/zh/enterprise-cloud@latest/rest/guides/using-pagination-in-the-rest-api)。\n\n## 捕获错误\n\n### 捕获所有错误\n\n有时，GitHub REST API 将返回错误。 例如，如果访问令牌已过期或省略了必需的参数，则会收到错误。 Octokit.rb 在收到除 `400 Bad Request`、`401 Unauthorized`、`403 Forbidden`、`404 Not Found` 和 `422 Unprocessable Entity` 以外的错误时，会自动重试请求。 如果在重试后仍发生 API 错误，Octokit.rb 会引发一个错误，其中包含响应的 HTTP 状态代码 (`response.status`) 和响应头 (`response.headers`)。 应在代码中处理这些错误。 例如，可以使用 try/catch 块来捕获错误：\n\n```ruby copy\nbegin\nfiles_changed = []\n\niterator = octokit.paginate.iterator(\"GET /repos/{owner}/{repo}/pulls/{pull_number}/files\", owner: \"github\", repo: \"docs\", pull_number: 22809, per_page: 100)\niterator.each do | data |\n    files_changed.concat(data.map {\n      | file_data | file_data.filename\n    })\n  end\nrescue Octokit::Error => error\nif error.response\nputs \"Error! Status: #{error.response.status}. Message: #{error.response.data.message}\"\nend\nputs error\nend\n```\n\n### 处理预期的错误代码\n\n有时，GitHub 使用 4xx 状态代码来指示非错误响应。 如果您正在使用的终结点这样做，则可以为特定错误添加额外的处理措施。 例如，如果存储库未加星标，则 `GET /user/starred/{owner}/{repo}` 终结点将返回 `404`。 以下示例使用 `404` 响应来指示存储库未加星标；所有其他错误代码都被视为错误。\n\n```ruby copy\nbegin\noctokit.request(\"GET /user/starred/{owner}/{repo}\", owner: \"github\", repo: \"docs\")\nputs \"The repository is starred by me\"\nrescue Octokit::NotFound => error\nputs \"The repository is not starred by me\"\nrescue Octokit::Error => error\nputs \"An error occurred while checking if the repository is starred: #{error&.response&.data&.message}\"\nend\n```\n\n### 处理速率限制错误\n\n如果收到速率限制错误，可能需要在等待后重试请求。 当受到速率限制时，GitHub 会返回 `403 Forbidden` 错误，`x-ratelimit-remaining` 响应头的值将为 `\"0\"`。 响应标头将包含一个 `x-ratelimit-reset` 标头，该标头告知当前速率限制窗口重置的时间（以 UTC 纪元秒为单位）。 可以在 `x-ratelimit-reset` 指定的时间后重试请求。\n\n```ruby copy\ndef request_retry(route, parameters)\n begin\n response = octokit.request(route, parameters)\n return response\n rescue Octokit::RateLimitExceeded => error\n reset_time_epoch_seconds = error.response.headers['x-ratelimit-reset'].to_i\n current_time_epoch_seconds = Time.now.to_i\n seconds_to_wait = reset_time_epoch_seconds - current_time_epoch_seconds\n puts \"You have exceeded your rate limit. Retrying in #{seconds_to_wait} seconds.\"\n sleep(seconds_to_wait)\n retry\n rescue Octokit::Error => error\n puts error\n end\n end\n\n response = request_retry(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 2)\n```\n\n## 利用响应功能\n\n如果请求成功，`request` 方法将返回响应对象。 响应对象包含 `data`（端点返回的响应正文）、`status`（HTTP 响应代码）、`url`（请求的 URL）和 `headers`（包含响应头的哈希）。 除非另外指定，否则响应正文会采用 JSON 格式。 某些终结点不返回响应正文；在这些情况下，将省略 `data` 属性。\n\n```ruby copy\nresponse = octokit.request(\"GET /repos/{owner}/{repo}/issues/{issue_number}\", owner: \"github\", repo: \"docs\", issue_number: 11901)\n puts \"The status of the response is: #{response.status}\"\n puts \"The request URL was: #{response.url}\"\n puts \"The x-ratelimit-remaining response header is: #{response.headers['x-ratelimit-remaining']}\"\n puts \"The issue title is: #{response.data['title']}\"\n```\n\n同样，`paginate` 方法会返回响应对象。 如果 `request` 成功，则 `response` 对象包含数据、状态、URL 和标头。\n\n```ruby copy\nresponse = octokit.paginate(\"GET /repos/{owner}/{repo}/issues\", owner: \"github\", repo: \"docs\", per_page: 100)\nputs \"#{response.data.length} issues were returned\"\nputs \"The title of the first issue is: #{response.data[0]['title']}\"\n```\n\n## 示例脚本\n\n下面是使用 Octokit.rb 的完整示例脚本。 该脚本导入 `Octokit` 并创建新的 `Octokit` 实例。 如果要使用 GitHub App 而不是 personal access token 进行身份验证，则需要导入并实例化 `App` 而不是 `Octokit`。 有关详细信息，请参阅本指南中的[使用 GitHub App 进行身份验证](#authenticating-with-a-github-app)。\n\n```\n          `get_changed_files` 函数获取为拉取请求更改的所有文件。 \n          `comment_if_data_files_changed` 函数调用 `get_changed_files` 函数。 如果拉取请求更改的任何文件的文件路径中包含 `/data/`，则该函数将对拉取请求进行注释。\n```\n\n```ruby copy\nrequire \"octokit\"\n\n octokit = Octokit::Client.new(access_token: \"YOUR-TOKEN\")\n\n def get_changed_files(octokit, owner, repo, pull_number)\n files_changed = []\n\n begin\n iterator = octokit.paginate.iterator(\"GET /repos/{owner}/{repo}/pulls/{pull_number}/files\", owner: owner, repo: repo, pull_number: pull_number, per_page: 100)\n iterator.each do | data |\n     files_changed.concat(data.map {\n       | file_data | file_data.filename\n     })\n   end\n rescue Octokit::Error => error\n if error.response\n puts \"Error! Status: #{error.response.status}. Message: #{error.response.data.message}\"\n end\n puts error\n end\n\n files_changed\n end\n\n def comment_if_data_files_changed(octokit, owner, repo, pull_number)\n changed_files = get_changed_files(octokit, owner, repo, pull_number)\n\n if changed_files.any ? {\n   | file_name | /\\/data\\//i.match ? (file_name)\n }\n begin\n comment = octokit.create_pull_request_review_comment(owner, repo, pull_number, \"It looks like you changed a data file. These files are auto-generated. \\n\\nYou must revert any changes to data files before your pull request will be reviewed.\")\n comment.html_url\n rescue Octokit::Error => error\n if error.response\n puts \"Error! Status: #{error.response.status}. Message: #{error.response.data.message}\"\n end\n puts error\n end\n end\n end\n\n# Example usage\nowner = \"github\"\nrepo = \"docs\"\npull_number = 22809\ncomment_url = comment_if_data_files_changed(octokit, owner, repo, pull_number)\n\nputs \"A comment was added to the pull request: #{comment_url}\"\n```\n\n> \\[!NOTE]\n> 这只是一个基本示例。 实际上，你可能想要使用错误处理和条件检查来处理各种场景。\n\n## 后续步骤\n\n要详细了解如何使用 GitHub REST API 和 Octokit.rb，请浏览以下资源：\n\n* 要详细了解 Octokit.rb，请参阅 [Octokit.rb 文档](https://github.com/octokit/octokit.rb/#readme)。\n* 要查找有关 GitHub 的可用 REST API 端点的详细信息，包括其请求和响应结构，请参阅 [GitHub REST API 文档](/zh/enterprise-cloud@latest/rest)。"}