- 开发工具
- 数据可视化
【免费下载链接】star-history
The de facto GitHub star history graph.
Star History 的 backend/CLAUDE.md 定位为面向 Claude Code 的项目导览,但其内容浓缩了整个后端服务的架构骨架:一个基于 Hono 的服务器,专门负责把 GitHub 仓库的 Star 历史数据渲染成可嵌入、可缓存的 SVG 图表。本文将以此文档为核心骨架,逐层深入源码(backend/main.ts、backend/token.ts、backend/cache.ts 等),讲清从 Token 管理、数据抓取、LRU 缓存到 SVG 渲染优化的完整调用链,并给出可直接落地的开发、构建与配置实操。
一、整体架构:一条从 GitHub API 到 SVG 的流水线
该后端是一个典型的“单一职责”服务:不承载页面、不做数据库持久化,只暴露一个核心图表接口。根据 backend/CLAUDE.md 的 Architecture 章节,核心数据流可归纳为五个阶段:
- Token 管理(backend/token.ts):从环境文件中读取并轮换 GitHub API Token,规避单 Token 的速率限制。
- API 请求(shared/common/api.tsx):向 GitHub API 抓取 Star 历史原始数据。
- 缓存(backend/cache.ts):LRU 缓存,官方文档给出的容量指标为“10K 仓库、1GB 上限、24 小时 TTL”,缓存对象包括 Star 记录与 Logo URL。
- 图表生成(shared/packages/xy-chart.tsx):基于 D3 的 SVG 图表渲染,运行在 JSDOM 模拟的 DOM 环境里。
- SVG 优化:使用 SVGO 对渲染结果做多轮压缩,以最小化带宽占用。
这套流水线在 backend/main.ts 的/svg端点中完整串联:先查渲染结果缓存,命中即返回;未命中则查 Star 数据缓存,再未命中则携带 Token 去 GitHub 拉取数据,随后走 JSDOM + D3 渲染、SVGO 压缩、写回缓存的链路。
二、开发与构建命令
后端独立于前端维护自己的 TypeScript 工程,命令定义在 backend/package.json:
# 开发模式:tsx 直接运行,带热重载 pnpm dev # 构建:tsc 编译 TypeScript 项目 pnpm build依赖集中在 backend/package.json 中,核心运行库包括:
- 服务框架:
hono(Web 框架)、@hono/node-server(Node 运行时适配); - 渲染管线:
jsdom(DOM 模拟)、d3-axis / d3-scale / d3-selection / d3-shape(图表绘制)、svgo(SVG 优化)、satori(OG 卡片 HTML 到 SVG 转换); - 数据与基础设施:
axios(GitHub API 请求)、lru-cache(缓存)、dayjs(日期处理)、winston(日志)。
三、GitHub Token 的加载与轮换机制
3.1 token.env 文件与 ENVPATH
文档明确给出了 Token 环境要求,结合 backend/token.ts 源码可以还原完整约定:
- 服务启动时需要一个
token.env文件,内容为 GitHub Token,每行一个; - 本地开发:通过环境变量
ENVPATH指定 token 文件位置,例如ENVPATH=PATH_TO_YOUR_FILE pnpm dev; - 生产环境:默认读取仓库根目录下的
./token.env(ENV_PATH_IN_RENDER常量,即process.env.ENVPATH || "./token.env")。
也就是说,Token 文件路径的解析优先级是:ENVPATH环境变量 > 默认的./token.env。
3.2 启动时的 Token 校验与冷启动退出
initTokenFromEnv()(backend/token.ts)在服务启动前执行三件事:
- 检查文件是否存在、内容是否为空,否则直接
process.exit(-1)拒绝启动; - 逐行切分 Token(兼容
\r?\n换行),并调用api.getRepoStargazersCount("star-history/star-history", token)实测校验每个 Token 是否可用,不可用的 Token 会被剔除(日志中只打印前 8 位 + 后 4 位,避免泄露); - 若没有任何可用 Token,同样
process.exit(-1)终止进程。
3.3 轮换与冷却:应对速率限制
GitHub 未认证请求有严格的速率限制,单 Token 高并发容易触发 403。getNextToken()与markTokenExhausted()(backend/token.ts)实现了“轮流调度 + 限流冷却”:
getNextToken():以循环索引轮换使用可用 Token;若某个 Token 处于冷却期(exhaustedUntil中记录了过期时间戳)则跳过,全部冷却则返回null;markTokenExhausted(token):当请求返回 403 时调用,将该 Token 冷却15 分钟(COOLDOWN_MS = 15 * 60 * 1000);- 主流程中,当
getNextToken()返回null时,backend/main.ts 会返回 503“All GitHub API tokens are rate-limited, try again later”,配合 CDN 缓存兜底。
四、三层 LRU 缓存体系
backend/cache.ts 与文档描述的“10K repos / 1GB / 24h TTL”完全对应,且实际实现了三层独立缓存,每一层的关键参数如下:
| 缓存 | 键 | 条目上限 | 内存上限 | TTL | 说明 |
|---|---|---|---|---|---|
cache(Star 数据) | repo 名 | 10,000 | 1 GB | 24 h | { starRecords, starAmount, logoUrl },单仓库数据约 896 字节 |
svgCache(渲染结果) | 规范化查询串 | 2,000 | 400 MB | 24 h | 已渲染并压缩的图表 SVG |
ogCardCache(OG 卡片) | repo 名 | 1,000 | 200 MB | 24 h | 1200×630 的分享卡片 SVG |
三层缓存统一使用lru-cache的maxSize+sizeCalculation做内存维度控制:
- Star 数据层用
utils.calcBytes(value)计算实际字节数; - SVG 层用
Buffer.byteLength(value)计算字符串字节数。
缓存并非“黑盒”:recordCacheHit / recordCacheMiss / getAllCacheStats(backend/cache.ts)为每一层维护命中/未命中计数器,并输出entries、memory、hits、misses、hitRate统计,通过/healthz端点暴露(详见第八节),是排查“为什么没走缓存”的直接手段。
五、核心端点/svg全参数解析
5.1 查询参数规范化
在真正处理图表请求之前,backend/main.ts 会先做一次301 重定向规范化:把repos参数统一转为小写(GitHub 仓库名不区分大小写)后再拼接回查询串重定向。这样做的好处正如代码注释所言:让 CDN 对同一张图只缓存一个条目,避免大小写不同的 URL 击穿缓存。
5.2 参数清单与默认值
主端点(backend/main.ts)接受以下查询参数:
| 参数 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
repos | 逗号分隔的owner/repo列表 | 必填,缺失返回 400 | 单次最多MAX_REPOS_PER_REQUEST(20 个,见 backend/const.ts) |
style | landscape1 | 空 | 开启 OG 卡片模式,返回 1200×630 分享图 |
type | date/timeline | Date | 坐标轴模式;也兼容date、timeline布尔型查询参数 |
size | mobile/laptop/desktop | laptop | 图表宽度,非法值回落到laptop |
theme | dark/ 其他 | light | 明暗主题 |
transparent | true | false | 透明背景 |
logscale | 任意值,false除外 | 关闭 | 对数坐标轴 |
legend | bottom-right/ 其他 | top-left | 图例位置 |
文档中提到“Single/svgendpoint that accepts query params (repos, type, size, theme, transparent)”,源码将这一清单扩展为完整的参数矩阵,并在 backend/main.ts 中逐一解析。以type的解析逻辑为例:优先读type参数,其次兼容旧式?timeline、?date无值标记的写法;logscale只要出现(且不等于字符串"false")即开启对数轴。
5.3 图表类型与尺寸
对应文档的 Chart Types 与尺寸说明:
- Date 模式:X 轴显示真实日期,适合观察项目在不同时间点的增长节奏;
- Timeline 模式:X 轴显示从仓库创建起算的相对时间,适合对齐比较多个创建时间不同的仓库。
尺寸由getChartWidthWithSize()(backend/utils.ts)映射:
mobile -> 600px laptop -> 800px desktop -> 1000px5.4 一次典型请求的完整链路
以文档给出的示例请求为蓝本(backend/main.ts):
/svg?repos=star-history/star-history&type=timeline&logscale&legend=bottom-right其内部处理顺序为:
- 校验
repos非空、数量不超过 20; style非landscape1,进入普通图表分支,解析 theme / transparent / type / logscale / legend / size;- 以规范化后的查询串为键查
svgCache,命中直接返回带缓存头的 SVG; - 未命中则逐 repo 查 Star 数据缓存,缺失的 repo 集合携 Token 调用
getRepoData()(shared/common/chart.tsx)抓数据与 Logo(Logo 并行转 base64); - 数据经
convertDataToChartData()(shared/common/chart.tsx 起)转换为图表坐标系; - JSDOM 创建 DOM,D3 的
XYChart渲染 SVG; fixJsdomSvgCasing()修复大小写,SVGO 压缩,写回svgCache并返回。
响应头固定为Content-Type: image/svg+xml;charset=utf-8与Cache-Control: public, s-maxage=86400, max-age=86400(backend/main.ts),即上下两层各缓存 24 小时。
六、数据抓取:如何用有限请求重建 Star 历史
Star 历史数据本身不在 API 的“一次返回”里,需要通过分页的stargazers端点重建。shared/common/api.tsx 的getRepoStarRecords()做了两处关键优化:
- 分页采样:通过响应头
Link解析总页数,当页数超过maxRequestAmount(后端默认 16,见 backend/const.ts)时,不是逐页拉取,而是均匀采样中间页,再结合每页首条记录的starred_at时间戳与“该页起始位置即当时 Star 数”的推论,重建出稀疏但趋势完整的星标时间线; - 末点锚定:最后调用
getRepoStargazersCount()取当前总 Star 数,把“现在”这一时间点写入记录,保证曲线终点永远是最新值。
错误处理在 shared/common/chart.tsx 中分层:404 返回“Repo not found”,403 返回“rate limit exceeded”并触发 Token 冷却,401 返回 Token 无效;对于 404 与 501(无 Star 历史),后端会返回一条 count 为 0 的占位记录,从而让该 repo 也能写入缓存,避免对坏请求反复打 GitHub。
七、SVG 渲染与优化细节
图表本体由 shared/packages/xy-chart.tsx(D3 + 前端共用的图表库)负责,后端在其外层补了两道“后端特有”的工序:
7.1 JSDOM 大小写修复
D3 在生成滤镜(如feTurbulence、feDisplacementMap)时会输出驼峰命名,而 JSDOM 解析 SVG 时会强制小写,导致某些渲染器下滤镜失效。fixJsdomSvgCasing()(backend/utils.ts)在序列化后做精确替换,把元素名与属性名(filterUnits、baseFrequency、xChannelSelector、yChannelSelector)恢复为规范写法。这是“JSDOM 里跑 D3”这类方案最容易踩的坑,值得在自建同构渲染时直接复用。
7.2 SVGO 多轮压缩
const optimized = optimize(svgContent, { multipass: true }).data;(backend/main.ts)multipass: true表示反复优化直至收敛,对内联的 base64 Logo 与坐标轴元素做最大化瘦身,直接决定每次嵌入 README 的带宽成本。
八、运维面:健康检查、日志与容器化
8.1 /healthz 与缓存观测
/healthz(backend/main.ts)返回 JSON,包含status: "OK"、commit(由环境变量GIT_COMMIT注入)以及三层缓存的完整统计(条目数、内存占用、命中率)。配合请求日志中间件(backend/main.ts,输出方法、路径、状态码与耗时),可以低成本地观测“缓存命中率是否健康、CDN 是否在替上游挡流量”。
8.2 日志与错误处理
日志基于winston(backend/logger.ts),级别由LOG_LEVEL环境变量控制(默认info),输出带时间戳与控制台着色;onError全局处理器(backend/main.ts)统一记录错误堆栈并返回 500。
8.3 Docker 部署
backend/Dockerfile 展示了生产部署形态:基于node:20-alpine,用 corepack 启用 pnpm 9,分层安装根目录共享依赖与 backend 依赖,并把shared/、gh/data/repos.json一并复制进镜像(OG 卡片模式依赖gh数据集中的仓库属性与排名),最终通过tsx main.ts启动,监听8080 端口。
九、共享代码的边界
文档特别强调:图表代码、API 客户端与类型定义全部放在根目录shared/(与前端共用),后端通过../shared/相对路径引用。这意味着对图表样式、坐标轴逻辑或数据类型结构的改动,会同时影响前端页面与后端 SVG 服务——在修改 shared/common/chart.tsx、shared/packages/xy-chart.tsx 等文件时,需要回归验证/svg的产物形态。这也解释了为什么后端依赖中同时出现 React 类型与 D3 相关包:渲染层本质是“复用前端图表库在服务端出图”。
结语
backend/CLAUDE.md用寥寥数十行勾勒出的,是一个高度工程化的“API 数据 → 服务端图表”闭环:Token 轮换与冷却应对 API 配额、三层 LRU 缓存对抗重复抓取、分页采样压低请求量、JSDOM + D3 + SVGO 实现服务端出图,最后用 CDN 友好的响应头与 301 规范化放大缓存效益。对于任何想构建“嵌入型图表服务”或“服务端 SVG 渲染”的开发者,这套组合的每一环——尤其是getRepoStarRecords的采样策略与fixJsdomSvgCasing的兼容性修补——都具备直接的借鉴价值。
- 开发工具
- 数据可视化
【免费下载链接】star-history
The de facto GitHub star history graph.
相关推荐
Codex-X Star History Worker:基于 Cloudflare Worker 的仓库 Star 历史 SVG 图表服务实战指南
Codex X Star History Worker:基于 Cloudflare Worker 的仓库 Star 历史 SVG 图表服务实战指南 本文以仓库中
桌面应用开发者工具AI 应用star-history 新版深度解读:GitHub Star 历史图的技术栈重写与图表增强功能源码剖析
star history 新版深度解读:GitHub Star 历史图的技术栈重写与图表增强功能源码剖析 star history 是一个专门把 GitHub
开发工具数据可视化GitHub星标历史数据挖掘终极指南:基于star-history的深度分析
GitHub星标历史数据挖掘终极指南:基于star history的深度分析 star history是一款强大的GitHub星标历史数据可视化工具,它能帮助开
开发工具数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考