系列帖子
A Tour of Go 简体中文站完成正式部署以后,我开始补齐生产环境的访问统计。
这次准备接入两套已经在博客中长期使用的统计服务:Google Analytics 4 和百度统计。
一开始看,这件事似乎很简单:把两段 JavaScript 放进页面 <head> 就可以了。
但真正开始处理以后,发现还涉及不少生产环境问题:
- Go Tour 现有代码是否已经预留统计代码入口;
- 本地预览与生产环境是否走相同的初始化逻辑;
- 首页
/和/tour/...是否共用同一个模板; - 统计代码应该直接写进模板,还是通过生产环境配置注入;
- systemd 应该怎样持久加载包含 HTML 和 JavaScript 的环境变量;
- 新版程序上线后,EdgeOne 会不会继续返回旧 HTML;
- 最后怎样证明统计代码不仅“出现在网页源码里”,而且真的向 Google 和百度完成了数据上报。
最终我没有为统计功能另外设计一套配置系统,而是继续利用项目原来就存在的 TOUR_ANALYTICS,并把验收分成三个层次:
源站
→ EdgeOne 公网
→ 浏览器真实上报

一、项目原来已经存在 AnalyticsHTML
首先需要确认的是:现有 Go Tour 到底有没有统计代码注入机制。
搜索代码以后,可以看到共享 Tour 模板 _content/tour/template/index.tmpl 中本来就存在:
{{.AnalyticsHTML}}
继续沿着 AnalyticsHTML 往上追踪,最终得到生产环境的大致调用链:
cmd/tour-i18n publish
→ 构建 cmd/tour-production
→ tour.NewProductionHandler(...)
→ RegisterHandlersLocale(...)
→ analyticsHTML = template.HTML(os.Getenv("TOUR_ANALYTICS"))
→ initTour(...)
→ renderIndex(...)
→ {{.AnalyticsHTML}}
也就是说,项目原本已经设计了一套生产统计代码入口:
TOUR_ANALYTICS
生产进程启动时读取这个环境变量,再把里面的内容作为 HTML 注入页面 <head>。
这里还有一个容易造成误解的地方:读取 TOUR_ANALYTICS 的逻辑位于 internal/tour/appengine.go。
但现在的 A Tour of Go 中文站并没有运行在 Google App Engine 上。
当前生产链路仍然是:
EdgeOne
→ Nginx
→ 127.0.0.1:3999
→ Go production binary
appengine.go 只是上游项目历史演进留下来的文件名。
这也再次说明,在维护一个基于上游项目改造而来的仓库时,不能仅凭文件名判断实际生产行为,还是应该沿着真正的调用关系继续追踪。
二、本地开发默认不加载生产统计代码
项目中的普通本地运行使用 local.go。
这里定义的是一个默认空值:
var analyticsHTML template.HTML
本地预览不会主动读取 TOUR_ANALYTICS。
而 production 初始化时才会执行:
analyticsHTML = template.HTML(os.Getenv("TOUR_ANALYTICS"))
这正好符合目前项目的需求:
本地开发
→ 默认没有生产统计
正式生产
→ 通过 TOUR_ANALYTICS 开启统计
因此没有必要再引入 .env 解析库,也没有必要增加新的命令行参数或者配置系统。
统计仍然只是一个生产部署配置。
三、真正缺失的是首页 /
继续审计以后发现,现有统计入口其实还不完整。
Tour 课程页面使用 _content/tour/template/index.tmpl,其中已经包含:
{{.AnalyticsHTML}}
但独立首页 / 使用的是另外一个模板:
_content/tour/template/home.tmpl
这个模板原来没有 AnalyticsHTML。
也就是说,如果此时直接给 production 设置 TOUR_ANALYTICS,结果会是:
/tour/...
→ 有统计代码
/
→ 没有统计代码
对于一个完整的独立站点来说,这显然不理想。
因此这次真正需要修改的 Go 项目代码其实非常少:让首页模板也复用已经存在的 AnalyticsHTML。
对应提交为:
cac4d6e feat: 为首页接入统一统计代码
修改后,首页 / 与课程页面 /tour/... 使用同一个 TOUR_ANALYTICS。
同时增加自动测试,分别覆盖两种状态:
analyticsHTML 为空
→ 首页和 Tour shell 都没有统计代码
analyticsHTML 已配置
→ 首页和 Tour shell 都恰好注入一次
这里没有增加第二套环境变量,也没有把 Google Analytics 或百度统计代码硬编码进模板。
四、百度统计继续与博客共用
我的 WordPress 中文站和英文站原来已经共用同一套百度统计代码。
在百度统计后台的“受访域名”报告中,可以分别看到:
www.shuijingwanwq.comen.shuijingwanwq.com
也就是说,即使多个子域共用一套百度统计代码,仍然可以按照受访域名拆分查看数据。
因此对于新的:
go-dev.shuijingwanwq.com
我最终没有新建一个独立的百度统计站点,而是继续复用博客现有的百度统计代码。
这样以后既可以查看所有站点的总体数据,也可以通过受访域名分别分析中文博客、英文博客和 A Tour of Go 中文站。
Google Analytics 也采用了相同思路,继续使用博客现有的 GA4 配置。
对我当前的使用场景来说,这比维护多套统计账号和多个独立配置更简单。
五、没有采用普通 .env,而是使用 systemd EnvironmentFile
接下来需要解决的是生产服务器上的配置存放问题。
Go 程序读取的是标准环境变量:
os.Getenv("TOUR_ANALYTICS")
它本身并不会自动读取项目目录中的 .env 文件。
而生产服务本来就由 systemd 管理,因此没有必要为了一个统计变量,再给 Go 项目增加 .env 加载代码。
服务器当前使用的 systemd 版本为 239,已经支持 EnvironmentFile=。
所以我最终建立了专用环境变量文件:
/etc/go-tour/go-tour.env
权限设置为:
root:root
600
然后通过 systemd drop-in:
/etc/systemd/system/go-tour.service.d/analytics.conf
加载这个文件:
[Service]
EnvironmentFile=/etc/go-tour/go-tour.env
最终形成的生产配置关系很清楚:
/etc/go-tour/go-tour.env
↓
systemd EnvironmentFile
↓
TOUR_ANALYTICS
↓
Go production binary
↓
{{.AnalyticsHTML}}
↓
首页 / + /tour/... 页面
Google Analytics 和百度统计两套代码都放在同一个 TOUR_ANALYTICS 中。
实际统计代码和具体统计 ID 不进入 Git 仓库。
六、为什么这个环境变量仍然需要严格控制权限
统计 ID 本身最终会出现在网页源码中,并不是严格意义上的服务器秘密。
但 TOUR_ANALYTICS 本身却值得更谨慎地管理。
原因在于程序执行的是:
template.HTML(...)
也就是说,Go 模板明确把这个环境变量里的内容当作可信 HTML,不会按照普通字符串再次转义。
这是统计代码能够正常注入 <script> 的基础,但同时也意味着:
谁能够修改
TOUR_ANALYTICS,谁实际上就能够向生产页面<head>注入任意 HTML 或 JavaScript。
因此,把环境变量文件设置为:
root:root
600
是合理的。
这也是为什么我没有把完整统计代码直接放进 Git,也没有把它作为普通启动参数写在命令行里。
七、先验证 systemd → Go → Tour 页面
systemd drop-in 配置完成以后,先执行:
systemctl daemon-reload
systemctl restart go-tour.service
然后从源站本机直接请求课程页面。
结果确认:
Google ID count: 2
Baidu ID count: 1
Google ID 出现两次是正常的,因为标准 GA4 代码中通常会分别出现在标签加载地址和 gtag("config", ...) 中。
百度统计 ID 出现一次。
这一步证明:
EnvironmentFile
→ TOUR_ANALYTICS
→ production binary
→ Tour shell
整条链路已经正常。
八、重新生成 production release,让首页也获得统计代码
不过此时生产服务器运行的还是旧 binary。
旧版已经支持 /tour/... 的 TOUR_ANALYTICS,但还没有包含刚刚为首页 / 增加的统计入口。
因此接下来重新生成了一份 production bundle:
/tmp/go-tour-release-20260812-zh-CN-cac4d6e
构建结果仍然保持:
locale=zh-CN
ready=103
pending=0
blocked=0
pages=103
articles=7
同时检查完整的 SHA256SUMS,所有文件均通过。
发布包里的两个模板也分别确认包含:
{{.AnalyticsHTML}}
之后把新 bundle 上传至:
/data/go-tour/releases/20260812-zh-CN-cac4d6e
先在独立 release 目录中完成服务器端 SHA-256 校验和 go-tour 用户访问权限检查,没有马上切换 production。
确认全部通过以后,才把:
/data/go-tour/current
切换至新 release,并重启 go-tour.service。
服务状态正常:
active
随后重新检查源站首页 / 和 /tour/welcome/1:
/ Google=2 Baidu=1
/tour/welcome/1 Google=2 Baidu=1
至此,Go 服务本身已经完成统计接入。

九、源站已经更新,EdgeOne 却仍然返回旧 HTML
这次部署中最值得记录的问题,反而出现在 CDN 层。
源站已经明确包含 Google Analytics 和百度统计代码,但通过公网域名访问时却得到:
Google ID count: 0
Baidu ID count: 0
继续查看响应头以后,发现:
EO-Cache-Status: HIT
Age: 4727
这意味着问题并不在 Go 服务,也不在 TOUR_ANALYTICS。
而是 EdgeOne 仍然在返回大约一个多小时前缓存的旧 HTML。
这也是一个很典型的生产环境问题:
源站验证成功,并不代表公网用户已经看到新版本。
尤其这次修改的是 HTML shell,而不是带版本号的静态 JS 或 CSS。
如果 CDN 缓存仍然命中,统计代码即使已经在源站上线,真实访客仍然不会获得它。
因此我在 EdgeOne 中执行了 go-dev.shuijingwanwq.com 的 Hostname 级缓存清除。
没有只刷新 / 或某一个课程页面,因为整个 /tour/... 下已经存在大量可能被缓存的 URL。
清除以后再次测试,响应变成:
EO-Cache-Status: MISS
并且公网 HTML 已经变为:
Google=2
Baidu=1
随后部分请求重新出现 HIT 也完全正常,因为此时缓存的已经是包含统计代码的新版页面。
这次最终整理出的生产验收摘要为:
Release: 20260812-zh-CN-cac4d6e
Service: active
EnvironmentFile: /etc/go-tour/go-tour.env
Source / Google=2 Baidu=1
Source /tour/welcome/1 Google=2 Baidu=1
Public / Google=2 Baidu=1
Public /tour/welcome/1 Google=2 Baidu=1
这一步让我把统计部署验收明确拆成了两层:
源站正确
≠
公网一定正确
CDN 必须单独验证。
十、代码出现在 HTML 中,还不代表统计真的工作了
到这里,其实仍然只能证明:
Google Analytics 和百度统计代码已经进入访客拿到的 HTML。
但统计服务是否真的工作,还需要浏览器运行 JavaScript 后才能确认。
因此最后一层验收放在 Firefox 开发者工具的 Network 面板中完成。
十一、Google Analytics:真实 g/collect 返回 204
首先过滤:
collect
可以看到请求发往:
analytics.google.com/g/collect
请求中包含当前 GA4 的 tid,同时 dl 参数已经正确记录:
https://go-dev.shuijingwanwq.com/tour/welcome/1
其中一次请求是 90% 页面滚动事件:
en=scroll
epn.percent_scrolled=90
响应状态:
204
这就不再只是“Google 标签加载成功”。
它证明浏览器已经真正执行 GA4 代码,并把 A Tour of Go 中文站上的实际用户事件发送给 Google Analytics。

g/collect 请求返回 HTTP 204,并携带当前课程页面地址,确认 Google Analytics 已产生真实事件上报十二、百度统计:hm.gif 返回 200
随后把 Network 过滤条件改成:
hm.baidu
可以看到两类主要请求:
hm.jshm.gif
其中 hm.js 是百度统计脚本,而 hm.gif 则包含实际统计上报。
请求参数中同样可以看到当前页面:
https://go-dev.shuijingwanwq.com/tour/welcome/1
响应状态为:
200 OK
因此百度统计也完成了真正的数据上报。

hm.gif 请求返回 HTTP 200,并携带当前课程页面地址,确认百度统计已经完成真实数据上报十三、最终形成三层统计验收
经过这次部署以后,我把生产统计功能的验收方式固定成了三个层次。
第一层是源站。
直接检查:
http://127.0.0.1:3999/
http://127.0.0.1:3999/tour/welcome/1
这里验证的是:
systemd
→ EnvironmentFile
→ TOUR_ANALYTICS
→ Go 模板
第二层是公网。
检查:
https://go-dev.shuijingwanwq.com/
https://go-dev.shuijingwanwq.com/tour/welcome/1
这里除了统计代码本身,还要注意:
EO-Cache-Status
Age
避免 CDN 仍然返回旧 HTML。
第三层才是浏览器真实请求。
Google Analytics 看:
analytics.google.com/g/collect
百度统计看:
hm.baidu.com
最终确认响应状态分别正常。
这样得到的结论比单纯“网页源码里有 <script>”可靠得多。
十四、把生产配置写进运维文档
全部验证完成以后,我又把这套配置补进了:
docs/PRODUCTION_RUNBOOK.md
对应提交:
0759457 docs: 补充生产统计配置与验收流程
文档现在明确记录了:
TOUR_ANALYTICS的统一统计入口;/etc/go-tour/go-tour.env;- systemd
EnvironmentFiledrop-in; - 首页
/和/tour/...共用统计机制; - 实际统计 ID 与完整代码不进入 Git;
- 修改环境变量后的服务重启要求;
- EdgeOne HTML 缓存可能继续返回旧页面;
- “源站 → 公网 → 浏览器”的三层验收方式;
- Google
g/collect和百度hm.gif的真实上报验证。
这样即使以后迁移服务器、重建 systemd 服务或者重新部署 A Tour of Go,也不会因为配置只存在于服务器上而忘记统计系统的接入方式。
十五、这次真正解决的不是“加两段统计代码”
回头看,这次工作真正有价值的部分,并不是把 Google Analytics 和百度统计的 JavaScript 复制到网页里。
更重要的是把统计能力纳入了现有生产架构:
生产配置
→ systemd
→ Go 服务
→ 模板
→ EdgeOne
→ 浏览器
→ 第三方统计服务
而且每一层都有可以单独验证的结果。
最终形成的是:
本地开发
→ 默认没有生产统计
生产环境
→ EnvironmentFile 注入 TOUR_ANALYTICS
首页 /
→ 有统计
/tour/...
→ 有统计
EdgeOne
→ 已确认新版 HTML
Google Analytics
→ g/collect HTTP 204
百度统计
→ hm.gif HTTP 200
这比直接把统计脚本硬编码进模板多走了一些步骤,但长期维护会简单得多。
下一次需要替换统计代码时,不需要重新修改 Go 源码;迁移生产服务器时,也有明确的环境变量文件和 systemd 配置可以恢复。
而 CDN 是否已经拿到新版 HTML、浏览器是否完成真实上报,也都有固定的验收方法。
对于一个刚刚完成正式发布的 A Tour of Go 多语言项目来说,这算是又补齐了一块比较重要的生产基础设施。
A Tour of Go 多语言翻译项目
本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。
项目入口:
✅ 在线学习:A Tour of Go 简体中文版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n
当前第一阶段已完成简体中文版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。


发表回复