重构 DevStats:用 EdgeOne Makers 与 AI Skills 打造开发者画像与辣评生成器
本文最后更新于 2026年7月8日 晚上
DevStats 是我做的一个开发者统计卡片与 AI 画像服务,脱胎自开源项目 github-readme-stats,用 Go 重写了渲染引擎,迁移到 EdgeOne Makers 托管,新增了 CNB 平台数据源,并融入了 AI 锐评和卡片推荐功能后改名为 DevStats。
最近对它做了一次全面重构,这篇文章记录整个过程,按几条主线展开:
- DevStats 从何而来
- “辣评”灵感来自哪里,浏览器抓取在其中扮演了什么角色
- 各个关键链路的构建思路与最终效果
生产地址:
- DevStats: https://dev-stats.mintimate.cn
DevStats 的来历
原项目
故事的起点是 anuraghazra/github-readme-stats,一个在 GitHub 社区广泛使用的开发者卡片生成工具。它通过简单的 URL 参数,让任何人都能在 README 里嵌入自己的 GitHub 数据卡片:
1 | |
效果就是一张包含你的 Star 数、Commit 数、PR 数的动态 SVG 卡片,放在 GitHub Profile 里非常好看。但它使用 JavaScript 实现,托管在 Vercel 上。由于后来 Vercel 对开源项目的免费额度大幅收紧,导致原项目的公开服务经常因超出限制而偶发性不可用。

自建初版
正是因为原版服务偶发性不可用,加上国内访问较慢,我决定参考该项目,用 Go 语言重写了卡片渲染引擎并自建服务,托管在 EdgeOne Makers 上,同时新增了对 CNB 平台的数据源支持——原项目只支持 GitHub,CNB 用户没有等价的卡片工具。
这个版本以 Mintimate/github-readme-stats 的名字发布:
- Go 渲染引擎:用 Go Cloud Functions 实现 SVG 卡片渲染,冷启动更低,并发性能更好,彻底告别了 Vercel 免费额度的限制和频繁不可用的尴尬
- EdgeOne Makers 托管:不用自己维护服务器,KV 存储、边缘 CDN、环境变量管理一体化
- CNB 数据源:在原有接口加入
platform=cnb参数,对 CNB 平台做专属数据抓取与渲染适配

升级改名
后来给项目加入了 AI Stats Agent:能对开发者画像进行多维评分、毒舌吐槽,并推荐最适合的卡片配方。功能扩展后,github-readme-stats 这个名字已经不能准确描述项目,于是升级改名为 **dev-stats**。
flowchart LR
Ref["参考\nanuraghazra/github-readme-stats\nJS / Vercel"]
V1["Mintimate/github-readme-stats\nGo 重写 / EdgeOne Makers / CNB 支持"]
V2["Mintimate/dev-stats\n新增 AI 锐评 + 卡片推荐"]
Ref -->|参考实现| V1
V1 -->|升级改名| V2
classDef ref fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#334155
classDef v1 fill:#fff7ed,stroke:#f59e0b,stroke-width:2px,color:#7c2d12
classDef v2 fill:#eef2ff,stroke:#4f46e5,stroke-width:3px,color:#312e81
class Ref ref
class V1 v1
class V2 v2

整体架构
当前 DevStats 的技术架构分两层:
flowchart LR
User["👤 访客 / 开发者"] --> Web["🎨 React 前端"]
Web --> GoEngine["🚀 Go 卡片渲染引擎"]
Web --> NodeAgent["🤖 Node.js AI Stats Agent"]
GoEngine --> API["GitHub / CNB API"]
NodeAgent --> KV[("📦 EdgeOne KV/Blob")]
NodeAgent --> API
classDef user fill:#fff7ed,stroke:#f59e0b,stroke-width:2px,color:#7c2d12
classDef ui fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#312e81
classDef go fill:#ecfeff,stroke:#0891b2,stroke-width:2px,color:#164e63
classDef agent fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#14532d
classDef infra fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#334155
class User user
class Web ui
class GoEngine go
class NodeAgent agent
class API,KV infra
- Go 渲染引擎:处理
/api、/api/top-langs、/api/pin等 SVG 卡片接口,极低冷启动延迟、支持高并发请求;兼容原项目所有参数,加platform=cnb即可切到 CNB 数据源 - Node.js AI Stats Agent:处理
/agents/chat、/agents/leaderboard、/agents/profile等接口,调用大模型生成开发者画像和辣评

EdgeOne Makers Skills
现在 AI Coding 已经是主流开发方式,越来越多的工具在探索如何让大模型更稳定、更有针对性地完成任务。其中比较公认的一个方向就是 Skills:通过预定义的指令集、工具清单和知识库引用,把”大模型能做什么、怎么做”规范化地表达出来,而不是每次靠临场发挥。
让我惊喜的是,EdgeOne Makers 平台本身就内置了对 Skills 的支持:开发者可以在项目里直接定义 Skills 文件,平台会自动识别并在 Agent 运行时注入对应的指令与工具,不需要自己拼接 Prompt 或手动管理工具注册逻辑。
为什么需要 Skills
纯粹的大语言模型就像”缸中之脑”:它掌握了海量的编程知识,但在被唤醒的那一刻,它无法感知真实世界——它不知道当前这个用户有多少 Star,也无法看到他的 Profile README 写了什么。
如果只给它一个用户名让它自由发挥,它最多能输出几段听起来有模有样但毫无根据的套话。
为了让 AI 成为真正能”拷打”开发者的助手,我们需要给它装上双眼和双手,也就是 Skills(工具箱)。
在 DevStats 里,AI Stats Agent 运行在 EdgeOne Makers 的 Node.js 运行时中,通过 @openai/agents SDK 定义了一组 Tool Calling 工具:
| 工具名 | 作用 |
|---|---|
browser_fetch |
抓取目标 URL 的渲染后文本,读取主页公开信号 |
inspect_github_user |
调用 GitHub API,获取仓库列表、语言分布、近期活跃度 |
inspect_cnb_user |
获取 CNB 平台项目、组织和公开履历 |
fetch_github_profile_readme |
读取用户 Profile 仓库中的 README 文本 |
compose_stats_recipe |
将分析结果映射为具体的 Stats 卡片配方 |
compose_readme_draft |
输出完整的 README Markdown 草稿,含辣评三段 |
有了这些 Skills,评估链路就变成:
flowchart LR
Input["访客填入用户名并触发评估"]
GH["inspect_github_user<br/>拉取仓库与活跃数据"]
README["fetch_github_profile_readme<br/>读取自我介绍"]
Browser["browser_fetch<br/>读取主页公开信号"]
Draft["compose_readme_draft<br/>生成辣评三段式报告"]
Recipe["compose_stats_recipe<br/>推荐卡片配方"]
Output["SSE 流式输出"]
Input --> GH
Input --> README
GH --> Draft
README --> Draft
Browser -.可选.-> Draft
Draft --> Recipe
Recipe --> Output
classDef input fill:#fff7ed,stroke:#f59e0b,stroke-width:2px,color:#7c2d12
classDef tool fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#14532d
classDef output fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#312e81
class Input input
class GH,README,Browser,Draft,Recipe tool
class Output output
AI 不再是凭空猜测,而是带着真实证据来”拷打”用户。
辣评的灵感
DevStats 最有趣的部分——对开发者画像的”辣评”——灵感源自开源项目 hikariming/ghfind(在线体验:ghfind.com)。
ghfind 的核心魅力在于:它不只客观地统计 Star 数和 Commit 频率,还会用大模型生成带有毒舌色彩的开发者评价,让枯燥的数字变得有趣。
DevStats 的 AI Stats Agent 在这个思路上继续延伸:通过 browser_fetch 和 API 抓取大量细节数据,让模型生产出能真正”刺痛灵魂”的评价。
浏览器抓取
精准辣评和泛泛而谈之间的差距,就在于 AI 拿到了多少真实、具体的细节。
单靠”用户有 10 个 Star,主用 Python”,模型只能产出类似”你的 Python 写得挺多的,继续加油哦”这种无聊废话。
但当 browser_fetch 和 inspect_github_user 把这些细节塞进 AI 的上下文:
- 某个仓库命名为
test或final-final,里面只有两个空文件,却留下 20 次 Commit - Profile README 声称”全栈架构师、K8s 专家”,但公开仓库 95% 是纯 HTML/CSS 练习
- Commit 时间大量集中在凌晨 2-4 点或周末,工作日一片空白
- 大量 Fork 仓库堆砌,原创项目稀少
模型就有足够的弹药生成真正有杀伤力的评价了。
三段式辣评
DevStats 的 AI 报告采用固定的三段式结构,这部分直接体现在 _prompt.ts 的系统提示词里:
| 段落 | 风格 | 内容 |
|---|---|---|
| promotional_summary | 促销文案 | 极尽夸张吹捧,用商业黑话包装用户特点 |
| objective_summary | 客观评价 | 基于 6 个维度评分(maturity / original_projects / contributions / influence / activity / community),各 1-20 分 |
| roast_summary | 毒舌吐槽 | 撕下吹捧,拿证据痛脚拷打,风格锋利幽默 |
综合评分对应五档评级:
| 评级 | 分数段 |
|---|---|
| 🔥 夯 | 90–100 |
| ⭐ 顶流 | 80–89 |
| 💪 高级 | 70–79 |
| 😐 平庸 | 50–69 |
| 🌱 入门 | 10–49 |
举个例子,对开源祖师爷 Linus Torvalds,其
roast_summary实际输出是这样的:“Linus Torvalds 的 GitHub 主页干净得像他写的 C 代码——没有 README,没有花里胡哨的 bio,连个星图都懒得搞。12 个仓库?对于一个拥有 31 万关注者的人来说,这仓库数量简直比他的脾气还稀少。但话说回来,人家发明了整个 Linux 和 Git,谁还需要 README 啊?"Talk is cheap, show me the code"——这话就是他说的,所以他的 README 就是一行代码:git push –force。”

通过 Skills 抓取 + LLM 语义提炼的闭环,DevStats 把一个冷冰冰的数据卡片生成器变成了一个有社交属性、让开发者自发传播的小工具。
前端组件化
原版 App.tsx 膨胀到 760 多行,把对话逻辑、卡片预览、排行榜、参数配置全部揉在一起。状态重绘范围大,改一处牵连全局,排查 bug 也很费力。
这次重构把它彻底拆分:
1 | |
每个组件聚焦单一职责,状态只在最小必要范围内流动。
排行榜也顺手做了 UX 净化:去掉了大量 emoji(金牌、火箭、奖杯),在暗黑模式下不再显得嘈杂;加上每页 10 条的分页控件,避免数据增长后列表无限拉长。同时修复了用户名大小写冲突导致同一个人在 Blob 中产生两份数据的问题,现在统一以平台 API 返回的权威格式(profile.login / profile.username)为准。
画像分享页
原来没有独立分享链接。访客想看别人的报告必须重新触发一次 AI 评估,白白消耗大量 Token 和时间。

这次新增了两个设计:
只读接口
agents/profile.ts:直接从 EdgeOne Makers Blob 存储读取已有的画像缓存,不触发任何 LLM 调用,响应时间从 10+ 秒缩短到 2s 以内。边缘路由中间件 middleware.js:DevStats 是 SPA,直接刷新
/u/github/Mintimate会 404。用 EdgeOne Makers 的 middleware 在边缘侧做透明重写:
1 | |

这样一来,分享链接可以直接传播,访客秒级打开,不消耗任何 AI 资源。
flowchart LR
Share["分享链接<br/>/u/github/Mintimate"]
Mid["EdgeOne 边缘中间件<br/>middleware.js"]
SPA["React SPA<br/>index.html"]
Profile["agents/profile 只读接口"]
KV[("EdgeOne Blob 缓存")]
Page["画像页极速加载<br/>雷达图 + 报告 + Timeline"]
Share --> Mid
Mid -->|透明重写| SPA
SPA --> Profile
Profile --> KV
KV --> Page
classDef entry fill:#fff7ed,stroke:#f59e0b,stroke-width:2px,color:#7c2d12
classDef edge fill:#ecfeff,stroke:#0891b2,stroke-width:2px,color:#164e63
classDef kv fill:#f8fafc,stroke:#64748b,stroke-width:2px,color:#334155
classDef out fill:#f0fdf4,stroke:#16a34a,stroke-width:2px,color:#14532d
class Share entry
class Mid,SPA edge
class Profile,KV kv
class Page out
Go 渲染引擎
DevStats 的 SVG 卡片渲染由 Go Cloud Functions 承担,在高并发下存在两个隐藏问题。

切片竞态
exclude_repo 切片被多个 Goroutine 共享时,存在底层数组竞态风险。修复方案是在操作前强制 Deep Copy:
1 | |
输入转义防注入
对所有的 username 输入强制做 url.QueryEscape(API 查询参数)和 url.PathEscape(路径段),防止特殊字符篡改后端请求语义:
1 | |
安全加固
Markdown 渲染的
DevStats 需要渲染大模型输出的 Markdown 和用户公开 Profile 文本。恶意用户可能在自己的 GitHub README 里写入 <img src="x" onerror="alert(1)">,如果原封不动渲染,就会在访客浏览器里执行非法脚本。
markdown.ts 里构建了三道防线:
关卡一:标签白名单
1 | |
关卡二:属性白名单 stripDangerousAttrs
即使是允许的标签,也只保留特定安全属性(href, src, alt, class 等),强制剥离所有 on* 事件监听器和 style 注入:
1 | |
关卡三:URL 协议防线 isSafeUrl
对所有 href 和 src 的 URL,只放行安全协议,阻断 javascript: 和 data: 伪协议:
1 | |
SSE 事件脱敏
这是一个容易被忽视的安全问题。
AI Agent 在流式对话时,SSE 事件中包含工具调用的详细入参和输出(如 GitHub API 原始响应)。如果直接将这些原始 SSE 帧持久化到 Blob,再在公开的 /agents/profile 接口中吐给访客,任何人通过浏览器 Network 面板就能看到这些内部数据。

处理方式是在写入 Blob 前做脱敏处理:
1 | |
访客在画像页能看到完整的 AI 分析路径时间线(”→ 检查 Profile → 读取 README → 生成报告”),但看不到任何工具调用的内部细节。
Blob 缓存与 Token 轮转
Blob 缓存策略
/agents/profile 接口的核心逻辑是优先读 Blob,只有缓存不存在时才触发完整 AI 评估。每次评估完成后,结果写入 Blob 并设置过期时间。缓存期内的分享访问完全不消耗 Token。
缓存的核心数据结构(agents/_cache.ts):
1 | |

缓存 key 按 analysis/v4/:platform/:username/:mode.json 的规则构造(L213-L221),CNB 用户名大小写敏感,GitHub 用户名统一小写,从根上避免大小写冲突导致的重复条目问题。
读取时先命中精确 key,命中失败再遍历 blob store 做兼容性模糊匹配(L39-L75):
1 | |
多 Token 轮转
GitHub 对匿名请求有严格限流(每小时 60 次)。DevStats 支持配置 GITHUB_TOKEN_1、GITHUB_TOKEN_2 等多个凭据,在排行榜全量重建等高频场景下轮换使用,避免触发 anonymous 限流:
1 | |
应用后的效果
重构完成后,DevStats 的整体形态变成了:
| 层级 | 负责的事情 |
|---|---|
| Go 卡片渲染引擎 | SVG 卡片生成,支持 10+ 种卡片类型,兼容原项目所有参数,毫秒级响应 |
| Node.js AI Stats Agent | 开发者画像分析、三段式辣评、排行榜维护 |
| React 前端 | 卡片参数配置、SSE 流式回放、独立画像分享页 |
| EdgeOne Makers 边缘层 | 路由重写、Blob 缓存、CDN 加速、全球就近访问 |
打开 dev-stats.mintimate.cn 后,主要有两个入口:
Agent 面板(AI Stats Agent):填入 GitHub / CNB 用户名,点击预设按钮触发评估。AI 会依次调用 Skills 工具链,通过 SSE 实时展示”思考过程 → 工具调用 → 生成报告”的完整路径。评估完成后可以直接获取个人分享页链接。
手动配置面板:支持实时选择主题、布局、显示参数,防抖自动刷新卡片预览,并生成可直接粘贴的 Markdown / HTML 代码。同时兼容原 github-readme-stats 的所有参数,原有链接无需修改,加 platform=cnb 即可切到 CNB 数据源。
对于已经跑过 AI 评估的用户,他人打开其分享链接 /u/github/:username 时,由于边缘中间件 + Blob 只读缓存的组合,响应几乎是即时的,完全不需要重新等待 AI 分析。

END
这次重构最大的收获,是把从 github-readme-stats 迁移过来的这套基础,真正扩展成了一个有独立功能特色的全栈项目:
- 原项目 Vercel 调用限制的问题,用 EdgeOne Makers Go 渲染引擎从根上解决了
- CNB 数据源让国内开发者不再只能展示 GitHub 数据
- 边缘路由中间件解决了 SPA 分享链接 404 的问题
- Blob 只读缓存把重复 AI 评估的 Token 成本降到了零
- SSE 脱敏和 Markdown XSS 白名单把开放平台的安全边界补齐
关于 AI Skills 的使用体会:AI 能产出什么质量的结果,很大程度取决于你给了它多少真实的上下文。browser_fetch + inspect_github_user + fetch_github_profile_readme 这一组组合下来,模型的辣评准确度比单纯给它一个用户名高出了不止一个量级。
DevStats 的分享和代码都是公开的:
如果你也想用 EdgeOne Makers 搭一个类似的 AI 全栈项目,欢迎参考这个仓库的结构,也欢迎去仓库提 Issue 或者 PR 一起完善。
