我做这个需求的原因很简单:平时已经在自己的管理后台处理内容、任务和项目,想查仓库、提交、健康状态或 Actions 时,如果还要另外打开 GitHub 页面,再来回切换,查起来不太方便。

所以我把 GitHub 常用的只读数据接到自己的后台里。登录后台后,可以直接查看这些信息,不需要为了看一次仓库状态再打开一个网页。

这个模块不是要替代 GitHub。GitHub 仍然是数据源,后台负责把常用信息集中展示,方便快速查阅和发现异常;需要创建 Issue、修改 PR 或管理 Workflow 时,仍然通过 GitHub 外链完成。

下面的教程按一套 Koa 后端与 Vue 管理端实际落地的 GitHub 数据模块整理。

要完成这个需求,需要把下面这条链路接通:

  1. 在 GitHub 创建一个权限尽量小的 fine-grained PAT。
  2. 把 Token 只配置在后端。
  3. 后端通过 GitHub REST API 和 GraphQL 读取数据。
  4. 后端给管理端提供统一的代理接口。
  5. 管理端展示总览、仓库健康、仓库详情和 Actions。
  6. 用现有权限系统控制谁可以访问。
  7. 通过测试、构建和后台页面完成验收。

本文使用的两个项目目录如下:

Text
D:\your-workspace\github-service
D:\your-workspace\github-admin

如果你的目录不同,只需要把命令中的路径替换成自己的路径。

一、为什么做,以及最终结构

为什么要放进自己的后台

自己的后台通常已经是日常工作的入口。文章、任务、数据统计和项目管理都在这里处理,如果 GitHub 数据也放进来,就可以在一个页面里快速确认:

  • 当前有多少仓库。
  • 哪些仓库最近有提交。
  • 哪些重点仓库有开放 Issue 或 PR。
  • 最近一次 Actions 是成功、失败还是仍在运行。
  • 哪些仓库最近没有 Release,或者读取数据时出现权限问题。

这里的重点是“查阅”,不是重新做一个 GitHub。页面只读数据,所有修改操作仍然跳转到 GitHub,避免把后台做成一套复杂的 GitHub 管理系统。

数据请求结构

浏览器不会直接请求 GitHub,也不会拿到 GitHub Token。

Text
管理后台浏览器
        |
        | 只请求 /api/admin/github/*
        v
github-service
        |
        | 从进程环境读取 GITHUB_TOKEN
        v
GitHub REST API / GraphQL API

Token 只存在于后端进程或生产服务器的环境变量中:

  • 不放到 Vue 前端。
  • 不写入数据库。
  • 不提交到 Git。
  • 不打印到日志。

这次模块包含四个页面:

页面 作用
GitHub 概览 账号资料、仓库数、Star、Fork、开放事项、贡献热力图、语言分布、最近提交和 Actions
仓库健康 全部仓库摘要,以及最近活跃仓库的精确 Issue、PR、Release 数据
仓库管理 查看本人拥有的仓库、仓库详情、语言占比和最近提交
Actions 监控 查看各仓库最近一次 Workflow Run 的状态、分支、事件和 GitHub 链接

这次没有做以下内容:

  • GitHub OAuth 登录。
  • GitHub App。
  • Issue、PR、Release 的修改。
  • Actions 重跑、取消。
  • 历史趋势数据库。
  • GitHub Token 管理页面。

二、开始前需要准备什么

1. 本地项目

需要能正常启动这两个项目:

Text
github-service
github-admin

后端是 Koa 项目,管理端是 Vue 3 + Vite 项目。

后端默认端口是 3000,管理端开发服务默认端口是 8002。如果端口被占用,Vite 会自动换一个端口,并在终端显示实际地址。

2. GitHub 账号

这个模块读取的是当前 GitHub 账号本人拥有的仓库。

后端调用的是:

Text
GET /user/repos

请求参数固定为:

Text
visibility=all
affiliation=owner
sort=updated
direction=desc

因此,Token 需要能访问你想展示的公开仓库和私有仓库。

3. 后台权限

普通后台用户还需要被分配 GitHub 菜单权限。

超级管理员默认可以访问。普通角色不会因为代码新增页面就自动获得权限,需要在后台的“访问管理”里分配。

三、创建 GitHub fine-grained PAT

1. 打开 Token 页面

登录 GitHub,依次打开:

Text
头像
-> Settings
-> Developer settings
-> Personal access tokens
-> Fine-grained tokens
-> Generate new token

也可以直接打开:

Text
https://github.com/settings/personal-access-tokens

2. 填写基础信息

建议这样填写:

  • Token name:例如 github-readonly-token
  • Expiration:按公司安全策略选择,不建议永久有效
  • Resource owner:选择自己的 GitHub 账号
  • Repository access:选择 All repositories,或者只选择需要展示的仓库

如果仓库属于组织,组织管理员可能需要审批这个 Token。审批完成前,接口可能返回 403。

3. 勾选 Repository permissions

这次代码实际用到的权限如下:

权限 选择 用在哪里
Metadata Read-only 读取仓库基础信息,GitHub 通常会自动要求
Contents Read-only 读取最近提交和语言统计
Issues Read-only 统计开放 Issue
Pull requests Read-only 统计开放 PR
Actions Read-only 读取 Workflow Runs

不要给以下权限:

  • Contents: Read and write
  • Issues: Read and write
  • Pull requests: Read and write
  • Actions: Read and write

本模块只读 GitHub 数据,不需要任何写权限。

4. 生成并保存 Token

点击 Generate token 后,GitHub 通常只会完整显示一次 Token。

Token 类似下面这样:

Text
github_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

不要把示例 Token 当成真实 Token 使用。

如果 Token 泄露,马上到 GitHub Token 页面撤销,再重新生成一个。

四、配置后端 GITHUB_TOKEN

1. Windows 本地临时配置

在启动后端的同一个 PowerShell 窗口中执行:

Text
cd D:\your-workspace\github-service
$env:GITHUB_TOKEN = 'github_pat_替换成你的真实Token'
node -e "console.log(process.env.GITHUB_TOKEN ? 'GITHUB_TOKEN 已加载' : 'GITHUB_TOKEN 未加载')"
npm run serve

注意:

  • 这个环境变量只对当前 PowerShell 窗口启动的后端进程有效。
  • 关闭窗口后,需要重新配置。
  • 不要把这条命令保存到脚本、截图或聊天记录中。

如果不希望 Token 直接出现在 PowerShell 命令历史里,可以使用安全输入:

Text
$secure = Read-Host '请输入 GITHUB_TOKEN' -AsSecureString
$env:GITHUB_TOKEN = [System.Net.NetworkCredential]::new('', $secure).Password
npm run serve

2. 本地长期配置

也可以把 GITHUB_TOKEN 设置为 Windows 用户环境变量,然后重新打开终端。

Text
[Environment]::SetEnvironmentVariable(
  'GITHUB_TOKEN',
  'github_pat_替换成你的真实Token',
  'User'
)

设置完成后,必须重新打开 PowerShell、VS Code 或启动服务的终端。

不要把真实 Token 写进:

Text
D:\your-workspace\github-service\.env

项目 README 已经约定:生产环境使用工作区外的环境文件;本地也建议使用终端环境变量,避免误提交。

3. Linux / 宝塔生产环境配置

当前生产 Docker Compose 使用的环境文件是:

Text
/www/env/github-service.env

编辑文件:

Text
sudo nano /www/env/github-service.env

增加一行:

Text
GITHUB_TOKEN=github_pat_替换成你的真实Token

保存后限制文件权限:

Text
sudo chmod 600 /www/env/github-service.env

进入后端项目目录,重新创建容器:

Text
cd /www/git/github-service
docker compose up -d --force-recreate

这里使用 --force-recreate,是因为修改 env_file 后需要让容器重新读取环境变量。只执行 docker compose restart,某些情况下不会重新载入新的环境变量。

检查容器是否拿到了变量,只输出 true 或 false,不要输出 Token 本身:

Text
docker exec github-api node -e "console.log(Boolean(process.env.GITHUB_TOKEN))"

输出 true 后,再检查后端健康状态:

Text
curl http://127.0.0.1:3000/health

五、后端模块是怎么工作的

后端主要涉及这几个文件:

Text
github-service/
├── modules/github/index.js
├── modules/github/index.test.js
├── routers/github.js
├── middleware/admin-permissions.js
├── modules/access/defaults.js
└── README.md

1. 读取 Token

GitHub 服务类从进程环境中读取 Token:

Text
getToken() {
  return String(this.env.GITHUB_TOKEN || '').trim();
}

没有配置 Token 时,服务返回“尚未配置”,不会发出 GitHub 请求。

2. 统一 GitHub 请求头

每次请求都使用 Bearer Token:

Text
Authorization: Bearer <GITHUB_TOKEN>
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28

Token 只拼接到后端发往 GitHub 的请求中,前端永远不会看到这个请求头。

3. 读取账号和仓库

概览请求先读取:

Text
GET https://api.github.com/user

再读取本人拥有的仓库:

Text
GET https://api.github.com/user/repos

后端会把 GitHub 原始响应转换成前端稳定使用的字段,例如:

Text
{
  id,
  name,
  full_name,
  owner_login,
  private,
  fork,
  archived,
  html_url,
  language,
  stargazers_count,
  forks_count,
  open_issues_count,
  updated_at,
  pushed_at
}

这样做的好处是前端不需要理解 GitHub 的全部字段,GitHub 字段变化时也只需要调整后端归一化逻辑。

4. 贡献热力图使用 GraphQL

仓库、提交和 Actions 使用 REST API。

贡献热力图使用 GraphQL 的 contributionCalendar,因为贡献日历数据由 GraphQL 提供:

Text
POST https://api.github.com/graphql

后端把 GraphQL 的驼峰字段转换成前端使用的下划线字段:

Text
totalContributions -> total_contributions
firstDay           -> first_day
contributionDays   -> contribution_days
contributionCount  -> count

如果贡献接口不可用,前端显示“贡献接口暂时不可用”,不会把失败误报为 0 次贡献。

5. 最近提交和 Actions 的限制

为了避免一次打开后台就请求过多:

  • 最近提交只处理按 pushed_at 排序后的 8 个仓库。
  • 每个仓库读取最近 3 条提交。
  • Actions 读取未归档、未禁用仓库的最近一次 Workflow Run。
  • Actions 请求并发数限制为 6。
  • 最近提交请求并发数限制为 4。

单个仓库失败时,其他仓库仍然返回,失败原因放入 warnings 数组。

6. 仓库健康数据

健康页使用独立的健康缓存。

深度检查只覆盖最近 pushed_at 排序后的 8 个未归档、未禁用仓库:

  • Issues API 统计开放 Issue,并排除带 pull_request 字段的项目。
  • Pull Requests API 使用 Link 响应头解析分页总数。
  • Releases API 读取最新 Release。
  • 没有 Release 时,GitHub 返回 404,后端转换成 null,不把它当成异常。

全部仓库的 open_issues_count 在页面上叫“开放事项”,因为 GitHub 的仓库摘要计数可能包含 PR。

六、后端接口清单

所有接口都使用统一响应格式:

Text
{
  "code": 200,
  "data": {},
  "message": "请求成功"
}

错误响应会额外包含 error_code,但不会返回 Token:

Text
{
  "code": 403,
  "data": null,
  "message": "GitHub Token 没有访问所需资源的权限",
  "error_code": "GITHUB_FORBIDDEN"
}

接口如下:

方法 路径 作用
GET /api/admin/github/status 查看 Token 是否已配置
GET /api/admin/github/overview 获取账号、仓库统计、贡献、语言、提交和 Actions 摘要
GET /api/admin/github/health 获取仓库健康数据
GET /api/admin/github/repositories 获取本人拥有的全部仓库
GET /api/admin/github/repositories/:owner/:repo/commits 获取指定本人仓库的最近提交
GET /api/admin/github/repositories/:owner/:repo/languages 获取指定本人仓库的语言字节占比
GET /api/admin/github/actions 获取各仓库最近一次 Workflow Run
POST /api/admin/github/refresh 清除 GitHub 缓存

status 返回示例

Text
{
  "code": 200,
  "data": {
    "configured": true,
    "cache_ttl_seconds": 600
  },
  "message": "获取 GitHub 配置状态成功"
}

health 返回示例

Text
{
  "code": 200,
  "data": {
    "generated_at": "2026-11-07T10:00:00.000Z",
    "cached": false,
    "summary": {
      "repository_count": 41,
      "all_open_work_items": 12,
      "active_repository_count": 8,
      "active_open_issues": 3,
      "active_open_prs": 2,
      "active_release_count": 4
    },
    "repositories": [],
    "warnings": []
  },
  "message": "获取 GitHub 仓库健康数据成功"
}

summary 中的数据范围要看清楚:

  • repository_count 和 all_open_work_items:全部本人拥有的仓库。
  • active_repository_count、active_open_issues、active_open_prs、active_release_count:最近活跃的重点仓库。

使用后台登录 Token 测试接口

这些是管理端接口,需要后台登录后的 JWT,不是 GitHub PAT。

PowerShell 示例:

Text
$headers = @{ Authorization = 'Bearer <后台登录JWT>' }
Invoke-RestMethod -Uri 'http://127.0.0.1:3000/api/admin/github/status' -Headers $headers

不要把 GitHub PAT 填进 Authorization 这里。浏览器访问后台接口时,管理端会自动把后台 JWT 加到请求头。

七、后端路由为什么不用手动注册

这套 Koa 项目会自动扫描 routers 目录。

app.js 中会调用:

Text
routerContainer.loadRouters(path.join(__dirname, 'routers'));

RouterContainer 会把 routers/github.js 自动注册成两组前缀:

Text
/api/github/*
/api/admin/github/*

所以新建 routers/github.js 后,不需要再去 app.js 手动 require。

路由文件只负责:

  1. 接收请求。
  2. 调用 githubService。
  3. 把结果包装成 code、data、message。
  4. 把错误转换成安全错误信息。

真正的 GitHub 请求、缓存、归一化和并发控制放在 modules/github/index.js 中。

八、权限系统接入

1. 服务端接口权限映射

在 middleware/admin-permissions.js 中增加:

Text
{
  prefix: '/api/github',
  keys: [
    'GitHub',
    '/github',
    'GitHubOverview',
    '/github/overview',
    'GitHubHealth',
    '/github/health',
    'GitHubRepositories',
    '/github/repositories',
    'GitHubActions',
    '/github/actions'
  ]
}

这样用户直接调用接口时,服务端也会再次检查权限。

不要只在前端隐藏菜单。用户可以直接在浏览器开发者工具或 Postman 里发送接口请求,所以服务端校验是必须的。

2. 默认菜单权限

modules/access/defaults.js 会初始化以下菜单:

Text
GitHub 数据       /github
GitHub 概览       /github/overview
仓库健康          /github/health
仓库管理          /github/repositories
Actions 监控      /github/actions

服务启动时会执行 ensureDefaultPermissionItems,缺少的菜单项会自动创建。

默认创建菜单不等于给所有普通角色授权。

3. 给普通角色分配权限

用超级管理员登录后台:

  1. 打开“访问管理”。
  2. 找到要授权的角色。
  3. 勾选“GitHub 数据”及需要的子页面。
  4. 保存。
  5. 让该角色重新登录,或者刷新权限。

如果用户能看到菜单但接口返回 403,优先检查角色是否只分配了前端菜单,没有真正保存服务端权限项。

九、管理端接入方式

管理端主要涉及:

Text
github-admin/
├── src/api/github.js
├── src/router/routers.js
├── src/views/Github/Overview.vue
├── src/views/Github/Health.vue
├── src/views/Github/Repositories.vue
├── src/views/Github/Actions.vue
├── src/views/Github/components/GithubPageHeader.vue
├── src/views/Github/components/GithubWarnings.vue
└── src/views/Github/github-utils.js

1. API 封装

src/api/github.js 统一使用 /api/admin/github 前缀:

Text
const PREFIX = '/api/admin/github';

所有请求都设置 skipErrorNotification,让页面自己展示未配置、权限错误、上游错误和部分失败状态。

API 封装包括:

Text
getGithubStatus()
getGithubOverview()
getGithubHealth()
getGithubRepositories()
getGithubActions()
refreshGithub()
getGithubRepoCommits(owner, repo, perPage)
getGithubRepoLanguages(owner, repo)

2. 路由配置

src/router/routers.js 中的 GitHub 路由归入“实验项目”:

Text
/github/overview
/github/health
/github/repositories
/github/actions

父路由 /github 默认重定向到 /github/overview。

3. 请求为什么能自动带后台身份

src/utils/request.js 的 axios 请求拦截器会自动读取后台登录 Token,并发送:

Text
Authorization: Bearer <后台JWT>

因此 GitHub 页面不需要自己处理登录 Token,也不能把 GitHub PAT 写到前端请求里。

Vite 本地开发配置会把 /api 请求代理到:

Text
http://127.0.0.1:3000

生产环境则由现有 Nginx 或部署配置转发到后端。

4. 页面状态要分开处理

每个页面都应该至少处理这些状态:

  • 加载中:显示 skeleton。
  • 未配置:提示配置后端 GITHUB_TOKEN。
  • 没有权限:显示 403 和访问管理入口。
  • GitHub 上游失败:显示安全错误。
  • 部分失败:保留成功数据,并显示 warnings。
  • 空数据:明确说明是没有数据,还是筛选后没有匹配结果。
  • 缓存数据:显示缓存时间和缓存标签。

这次 UI 优化还统一了:

  • 页面标题、说明、更新时间和刷新按钮。
  • 成功、失败、等待、缓存四类状态颜色。
  • 警告的影响范围、失败数量和展开详情。
  • 外链新窗口打开,并设置 noopener noreferrer。
  • 桌面端表格和手机端卡片。

十、如何从零实现一个类似模块

如果你要把这套方案迁移到另一个 Koa + Vue 管理后台,建议按这个顺序做。

第一步:先写后端服务类

先完成这些函数:

Text
getToken()
createHeaders()
get(path, params)
normalizeRequestError(error)
normalizeRepository(repository)
normalizeCommit(commit, repository)

先用测试固定字段,不要一开始就写页面。

第二步:实现最小接口

先实现:

Text
GET /status
GET /overview

确认 Token、/user、/user/repos 能正常工作后,再加入:

Text
GET /health
GET /repositories
GET /repositories/:owner/:repo/commits
GET /repositories/:owner/:repo/languages
GET /actions
POST /refresh

第三步:加缓存和并发限制

建议把缓存放在服务类实例里:

Text
overviewCache
healthCache
detailCache

每个缓存保存:

Text
data
expiresAt

本项目缓存时间是 10 分钟。

列表请求不要对几十个仓库无限并发。使用固定并发数,并让单个仓库失败只产生 warning,不影响整个列表返回。

第四步:接入服务端权限

先在接口权限映射里加 /api/github,再创建默认菜单。

前端菜单过滤只是体验层,服务端权限映射才是安全边界。

第五步:接入管理端

按下面顺序:

  1. 写 src/api/github.js。
  2. 写路由。
  3. 写页面加载、错误和空状态。
  4. 接入刷新按钮。
  5. 接入权限过滤。
  6. 最后做移动端布局。

不要先复制一整套复杂组件。四个页面真正需要共享的只有页面头部、告警展示和少量格式化函数。

十一、开发和运行命令

启动后端

Text
cd D:\your-workspace\github-service
npm install
npm run serve

开发时也可以使用:

Text
npm start

当前 package.json 中,npm start 使用 nodemon,适合修改后自动重启。

启动管理端

另开一个 PowerShell:

Text
cd D:\your-workspace\github-admin
npm install
npm run dev

浏览器打开终端显示的地址,通常是:

Text
http://127.0.0.1:8002

运行后端测试

Text
cd D:\your-workspace\github-service
npm test

GitHub 服务测试在:

Text
modules/github/index.test.js

重点覆盖:

  • 响应字段归一化。
  • 统计汇总。
  • GraphQL 贡献日历转换。
  • 缓存命中、过期和清除。
  • Token 未配置。
  • 401、403、404、429 错误。
  • 非本人仓库访问限制。
  • 单仓库部分失败。
  • Issue、PR、Release 健康数据。

运行管理端测试和构建

Text
cd D:\your-workspace\github-admin
node --test test/*.test.js
npm run build

GitHub 工具函数测试在:

Text
src/views/Github/github-utils.test.js

重点覆盖:

  • 仓库关键词、可见性、归档和 Fork 筛选。
  • Actions 失败、运行中、排队、成功排序。
  • 健康检查范围标签。
  • null 数据显示为短横线。
  • 非 200 响应的安全解析。

十二、后台验收清单

配置状态

  • 未设置 GITHUB_TOKEN 时,页面显示“尚未配置 GitHub”。
  • 设置 Token 并重启后端后,status 的 configured 变为 true。
  • 页面不显示 Token 内容。

总览页

  • 能看到 GitHub 账号头像、用户名、Followers 和 Following。
  • 仓库数、Star、Fork 和开放事项有数据。
  • 贡献数据不可用时,不会误显示为 0。
  • 语言分布较多时,低占比语言合并为“其他”。
  • 最近提交和 Actions 链接会新窗口打开。
  • 健康接口失败不会阻塞总览页面。

仓库健康页

  • 页面清楚区分全部仓库摘要和重点仓库精确检查。
  • 重点仓库显示 Issue、PR 和 Release。
  • 没有 Release 显示“暂无 Release”。
  • 权限失败显示短横线和原因,不显示成 0。
  • 筛选可以按检查范围、公开性、归档状态、Fork 和开放 PR。

仓库管理页

  • 关键词、公开/私有、活跃/归档、Fork 筛选正常。
  • 桌面端显示表格。
  • 手机端显示卡片。
  • 点击行可以打开详情。
  • 点击 GitHub 外链不会同时打开详情抽屉。
  • 抽屉中的语言请求失败时,提交列表仍然可以展示。
  • 移动端抽屉接近全屏,底部操作区固定可用。

Actions 页

  • 失败记录排在成功记录前面。
  • 运行中、排队中、成功数量正确。
  • 可以按仓库、状态、事件和分支筛选。
  • 没有 Workflow、筛选无结果和权限不足时,提示内容不同。
  • 页面只手动刷新,不自动轮询。

响应式

至少检查三个宽度:

Text
1440px:桌面表格和双栏内容
1024px:筛选栏换行、内容单栏
390px:卡片、移动端抽屉和横向热力图

十三、常见问题

1. 页面一直显示“尚未配置 GitHub”

依次检查:

  1. 后端进程启动前是否设置了 GITHUB_TOKEN。
  2. 修改环境变量后是否重启了后端。
  3. Docker 是否重新创建了容器。
  4. 生产环境文件是否真的是 /www/env/github-service.env。
  5. 容器内执行 Boolean(process.env.GITHUB_TOKEN) 是否返回 true。

2. 返回 GITHUB_TOKEN_INVALID

一般是:

  • Token 复制不完整。
  • Token 已过期。
  • Token 已被撤销。
  • 环境变量中多了引号或空格。

重新生成 Token 后,修改环境变量并重启服务。

3. 返回 GITHUB_FORBIDDEN

检查 Token 权限:

  • Metadata 是否为 Read-only。
  • Contents 是否为 Read-only。
  • Issues 是否为 Read-only。
  • Pull requests 是否为 Read-only。
  • Actions 是否为 Read-only。

如果是组织仓库,还要检查组织是否要求管理员审批 fine-grained Token。

4. 仓库健康页的 PR 是短横线

短横线不是 0,表示这项数据没有成功读取。

优先检查 Pull requests: Read-only 权限和 GitHub 返回的 warning。

5. 最近 Release 显示“暂无 Release”

这通常不是错误,而是仓库没有 Release。

只有 GitHub 返回 404 时,后端才会把最新 Release 转换为 null;页面会显示“暂无 Release”。

6. 贡献热力图显示不可用

贡献日历走 GraphQL,不要只检查 REST API 权限。

先看页面的 warning,再确认:

  • Token 是否有效。
  • GitHub 账号是否允许显示相关贡献数据。
  • 是否只把“无数据”误认为“请求失败”。

7. 页面有菜单,但访问接口返回 403

这是前端菜单权限和服务端接口权限没有同步。

到“访问管理”里重新检查:

  • GitHub 数据父菜单。
  • GitHub 概览。
  • 仓库健康。
  • 仓库管理。
  • Actions 监控。

然后重新登录普通用户。

8. 返回 GITHUB_RATE_LIMITED

不要连续点击刷新。

本模块默认缓存 10 分钟,并限制并发请求。确认页面显示的缓存时间,稍后再刷新。

十四、官方文档

阅读进度 0%