跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

如何在 Go 中实现支持 GitHub 动态徽章(SVG Badge)的服务

本文详解如何用 go 编写一个可嵌入 git hub readme 的动态 svg 徽章服务,并解决 github 代理层强缓存导致状态不更新的核心问题。关键在于正确设置 http 缓存控制头(如 `cache-control: no-cache`、`etag` 和 `last-modified`),绕过 github 的 cdn 缓存。 GitHub README 中的图片由 GitHub 的反向代理(基于 Fastly)缓存,即使你的 Go 服务返回 Cache-Control: no-cache,GitHub 仍可能忽略或覆盖该头,导致徽章“卡死”在旧状态。实测表明: 仅靠 no-cache 不足以强制刷新;必须配合强校验机制(如 ETag + If-None-Match 协商)和语义化响应头,才能让 GitHub 每次请求都回源获取最新状态 。 以下是一个生产就绪的 Go 徽章服务实现,已通过 GitHub README 实时切换验证(如每刷新一次页面即切换 “correct”/“wrong”): Comet AI 浏览器 下载 Comet AI 浏览器,体验由 Perplexity AI 驱动的革命性上网方式。内置 AI 助手可实时总结网页、跨标签页对比信息、自动执行任务。告别繁琐操作,让 AI 成为你的浏览副驾,大幅提升研究与工作效率。支持 Windows、macOS、Android 和 iOS。 下载
package main import ( "crypto/md5" "fmt" "log" "net/http" "time" ) var ( badges = map[string][]byte{ "correct": []byte(`solutionsolutioncorrectcorrect`), "wrong": []byte(`solutionsolutionwrongwrong`), } state = false lastMod = time.Now().UTC().Truncate(time.Second) // 确保 Last-Modified 可预测 ) func badgeHandler(w http.ResponseWriter, r *http.Request) { // ✅ 强制禁用缓存(GitHub 明确识别) w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") // ✅ 提供 Last-Modified 和 ETag(基于当前状态生成唯一指纹) etag := fmt.Sprintf(`"%x"`, md5.Sum([]byte(fmt.Sprintf("%t-%s", state, lastMod.Unix())))) w.Header().Set("ETag", etag) w.Header().Set("Last-Modified", lastMod.Format(http.TimeFormat)) // ✅ 支持条件请求:若客户端(GitHub)携带匹配 ETag,则返回 304 if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return } // ✅ 切换状态(真实场景中建议用原子操作或 channel 控制) state = !state key := "correct" if !state { key = "wrong" } // ✅ 设置内容类型与长度(避免 Transfer-Encoding: chunked 导致解析失败) w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Content-Length", fmt.Sprintf("%d", len(badges[key]))) // ✅ 写入 SVG 内容 w.Write(badges[key]) } func main() { http.HandleFunc("/view", badgeHandler) log.Println("Badge server listening on :8085") log.Fatal(http.ListenAndServe(":8085", nil)) }
关键要点说明: ETag 必须随状态变化而变化 :我们使用 md5(state + timestamp) 生成强 ETag,确保每次状态翻转都产生新指纹,触发 GitHub 回源。 Last-Modified 需为标准格式且稳定 :GitHub 会校验其合法性,使用 time.UTC().Truncate(time.Second) 避免毫秒级差异引发协商失败。 显式设置 Content-Length :防止 Go 默认启用分块传输(chunked encoding),某些 GitHub 代理对无长度的 SVG 响应处理异常。 Timing-Allow-Origin: https://github.com (可选增强):若需 前端 调试,可添加此头支持 跨域 Timing API,但非必需。 在 README 中正确嵌入:
![Dynamic Badge](http://your-domain.com/view)
⚠️ 注意: 不要加括号空链接语法 []() —— 这会导致 GitHub 渲染为纯文本而非图片。务必使用标准 Markdown 图片语法。 验证是否生效: 启动服务后,在 浏览器 中多次访问 /view,观察 SVG 是否交替变化; 将链接粘贴至 GitHub README 并提交; 硬刷新(Ctrl+Shift+R)README 页面 ,检查徽章是否实时响应(非首次加载缓存); 使用 curl -I http://your-domain.com/view 检查响应头是否包含 ETag、Last-Modified 和 Cache-Control: no-cache。 ? 提示:若仍被缓存,可临时在 URL 后添加时间戳参数(如 /view?t=123)作为兜底方案,但推荐优先修复 ETag 逻辑——它更规范、零带宽开销,且符合 GitHub 官方 badge 服务(如 Travis CI)的实践模式。

相关文章