概述 —— 这个页面做什么
在 服务 ▸ Web 的每张卡片上,独立访客 数字旁边并排放着两个图标,各自打开一个弹窗。
| 图标 | 弹窗 | 数据来源 |
|---|---|---|
| 人形 | 访客分析 | Google Analytics 4(Analytics Data API) |
| 放大镜 | 搜索流量 | Google Search Console(Search Analytics API) |
卡片的 nginx_meta_ct 访问情报告诉你的是 你的服务器收到了什么,而这两个弹窗告诉你的是 访客从哪里来、是哪些搜索词把他们带来的 —— 这类信息你的服务器日志里根本没有。
Site Kit 在 WordPress 上做的事,归根结底就是 替你组装报表请求。在 Laravel 上你自己组装同样能得到一模一样的数字,所以我们 既不用插件,也不用
google/apiclient—— 整件工作无非是「用私钥给 JWT 签名,再换成访问令牌」,而服务器原生就能完成签名。为了两个 REST 端点背上一整棵庞大的依赖树并不划算。
何时使用
- 某张卡片的流量涨了,你想知道是 哪个渠道、哪个来源 带来的。
- 你想知道带来展示与点击的 搜索关键词,以及每个关键词落在 哪个页面 上。
- 你想一眼看清受众构成 —— 新访客占比、设备构成、流量来源性质。
- 内容发布之后,你在跟踪展示次数、点击与平均排名。
页面构成
两个弹窗都分为 概览 / 关键词 / 流量统计 三个标签页(关键词标签页只存在于搜索弹窗中),因此是切换而不是一路下拉。期间选择器提供 7 / 28 / 90 天 —— 与服务端白名单的集合完全相同。
访客分析(GA4)
- 六个 KPI:访客、新访客、会话、页面浏览量、互动率、平均会话时长 —— 每个都附带与紧邻上一周期的变化。
- 三条构成比条:访客忠诚度(新访客/回访)、设备构成(桌面端 对 手机与平板)、流量来源性质(搜索 对 其他)。三者都是二分,因此共享同一套视觉语言;既然 KPI 已经给出了绝对数字,这里 只呈现比例。
- 趋势图:访客、会话与页面浏览量。
- 六个面板(流量统计标签页):渠道、来源、城市、国家/地区、设备、热门页面。
渠道和来源不是一回事。 渠道是归组之后的答案(
Organic Search),来源是具体的那一个(naver、github.com)。尤其对韩国的站点来说,是 naver 还是 google,意味着两段截然不同的获客故事。
搜索流量(Search Console)
- 五个 KPI:点击、展示次数、CTR、平均排名、关键词数量。
- 词云:大小与颜色表示展示次数,● 标出真正带来点击的关键词,数字则是
~1d / ~7d / all的展示次数。 - 关键词表:点击、展示次数、CTR、排名。
- 着陆页 × 搜索词:展开某个页面,即可看到经由它进入的关键词。
- 三个面板:着陆页、国家/地区、设备。
平均排名是按展示次数加权的。 若取简单平均,排名第 1 的一次展示就能抵消排名第 40 的一万次展示,把数字美化得离谱。
配置步骤
1. 创建服务账号(Google Cloud)
- Google Cloud Console → 选择或新建一个项目 → IAM 和管理 ▸ 服务账号 → 创建服务账号。不需要任何项目级角色。
- 打开该账号 → 密钥 → 添加密钥 ▸ 创建新密钥 ▸ JSON → 下载文件。
- 在 API 和服务 ▸ 库 中启用 Google Analytics Data API 与 Google Search Console API。
2. 在 InfraGuard 中登记
进入 服务 ▸ Web ▸ 网站管理 ▸ 该站点的编辑标签页,在 Google 集成 之下:
| 字段 | 值 | 从哪里找到 |
|---|---|---|
| GA4 媒体资源 ID | 仅数字(例如 550688232) |
GA4 管理 ▸ 媒体资源详情,或网址 #/a<account>p<property>/ 中 p 之后 的部分 |
| Search Console 资源 | sc-domain:example.com 或 https://example.com/ |
与 Search Console 资源选择器中显示的字符串完全一致 |
| 服务账号 JSON | 粘贴下载文件的 全部内容 | — |
GA4 媒体资源 ID 不是账号 ID。 在
#/a405285491p550688232/中,a之后的是账号,而p之后的才是媒体资源。Google 有意不区分「没有这个资源」和「没有权限」(资源是否存在本身就是信息),因此号码填错时看起来与权限问题一模一样。请先核对媒体资源 ID。
切勿从域名推断 Search Console 资源。 只有完成所有权验证的那个人才知道它是域名资源(
sc-domain:)还是网址前缀(https://…/),而选错时返回的是 空结果而不是错误。
3. 把服务账号邀请到每个资源
JSON 只是一个身份;访问权限是 按资源 授予的。复制保存后显示的 client_email,然后:
| 资源 | 位置 | 角色 |
|---|---|---|
| GA4 | 管理 ▸ 媒体资源 访问权限管理 ▸ + |
查看者 |
| Search Console | 设置 ▸ 用户和权限 ▸ 添加用户 | 完全 |
在 GA4 中这是 媒体资源 的访问权限管理,而不是 账号 的访问权限管理(左边一栏是账号,中间一栏才是媒体资源)。Search Console 必须是 完全 而不是受限 —— Search Analytics API 会拒绝受限用户。服务账号没有收件箱,所以请取消勾选「通过电子邮件通知」。
4. 测试连接
管理弹窗中的 [连接测试] 会用已保存的凭据把两个 API 各调用一次(最近 7 天),并分别报告每一项的结果。
| 状态 | 含义 |
|---|---|
ok |
Google 有响应。零行也仍然是 ok —— 新站点和 GSC 的延迟都不是错误 |
fail |
Google 自己的措辞,外加需要邀请的 client_email |
skip |
未配置资源,因此跳过(不算失败) |
完全没有凭据时它什么都不会调用(外发请求为零)。未保存的编辑不会被测试 —— 请先保存。
正确性规则 —— 这些数字为何这样算
以下都遵循 Site Kit 早已验证过的那些陷阱,每一条都有依据。
| 规则 | 原因 |
|---|---|
| 时间窗 截止到昨天 | 今天只是部分汇总,把它算进来会让右端永远塌陷 |
GA4 从 metricAggregations:['TOTAL'] 读取 totals[0] |
把带 limit 的行加总,会让总览数字偏低 |
每次 GA4 请求都带 hostName 的 in-list 过滤 |
一个媒体资源常常横跨多个主机,没有它卡片就会显示整个资源的流量。我们会把 该服务拥有的每一个域名(主域名、附加域名、www)都传进去,以便与卡片的 vts 合计完全一致 |
排行使用 keepEmptyRows:false + notExpression{emptyFilter} |
GA4 的 (not set) 常常压过真实的城市与来源 |
时间序列使用 keepEmptyRows:true |
零流量的那一天不能从坐标轴上消失 |
GSC 使用 dataState:'all' 并计入约 2 天延迟 |
否则最近几天会读成 0,看上去像是流量崩塌 |
域名资源用 page contains 限定范围 |
sc-domain: 覆盖所有子域名,兄弟站点的关键词会混进来 |
数据来源
| 对象 | 连接 / 位置 | 作用 |
|---|---|---|
infr_web_service.ga4_property_id · gsc_site_url |
default(000771) | 按站点的资源映射 |
infr_web_service.google_cred_enc · google_cred_email |
default(000772) | 按站点的服务账号(Crypt,只写) |
Analytics Data API properties/{id}:runReport |
外部 | 访客报表 |
Search Console searchAnalytics/query |
外部 | 搜索流量报表 |
服务:Services\Google\{GoogleTokenService, GoogleReportService, SiteInsightService};端点为 Service\WebController@insight{Visitors,Search,Test}。
缓存:报表响应会被缓存,且 缓存键包含执行身份(client_email)。同样的 URL 与请求体,换一个账号发起就是另一次授权;共享缓存键会把某个站点的缓存响应交给另一个站点。
凭据为何按站点保存
单一的全局 .env 密钥在这里 行不通。 已登记的站点分属不同的所有者,因而也属于不同的 Google 账号,而一个服务账号只能读取它被邀请进去的那些资源 —— 所以只要登记了两家客户的站点,就必然至少有一方一直失败。正因如此才有按站点粘贴的字段。
- 使用
Crypt(APP_KEY) 保存,并且 只写:模型会隐藏密文,UI 看到的永远只是一个布尔值,外加并不机密的client_email。私钥不会再出来。 - 保存时做三重校验:JSON 能解析、
client_email/private_key存在,以及 签名密钥检查接受这把密钥。粘贴被截断的情况很常见,一旦放行,之后就会以假的「权限不足」形式浮现,害你跑去翻 Google 的 IAM。 - 服务器级别的凭据仍作为 单账号部署的回退 保留;站点自己的凭据永远优先。
凭据字段 只有你动过它才会被发送。表单永远不会取回私钥,所以若把「没送来」当成「清除」,那么只改一条备注也会把凭据抹掉。只有显式清空才是删除。
访问权限
- 读取:任何已登录用户(
infr.auth,1 级以上)。它只是 读取 外部统计,对 InfraGuard 和 Google 都不做任何更改,因此没有二次验证,也没有审计记录(与 IP 信誉查询 相同的规矩)。 - 保存凭据/资源映射:
user_level >= 2(与保存网站相同),并记入infr_audit_log。
目标 只能通过已登记站点的 id 指定 —— 媒体资源 ID 不能作为参数传入。如果调用方可以指定资源,我们服务账号的权限就会变成读取 别人的 GA4/Search Console 资源的通道(与 目录的实时测试 相同的防 SSRF 规矩)。期间同样在服务端被白名单限定为 7/28/90。
疑难排查 · FAQ
- 卡片上没有图标 —— 站点必须 同时 具备凭据与资源映射,按钮才会出现。判定是按站点进行的,所以即使全局
.env为空,拥有自有凭据的站点照样可用。保存后重新加载页面。 - 每一项检查都是绿的,却仍然返回 0 —— 那是采集问题,不是查询问题。① 时间窗截止到昨天,所以今天才装上的代码仍在窗外。② 确认站点实际投放的衡量 ID(
G-…)确实属于你正在查询的那个媒体资源(GA4 管理 ▸ 数据流);如果数据落进了别的资源,你会永远读到 0。③ Search Console 有约 2 天延迟,新站点甚至可能还没有任何展示。 User does not have sufficient permissions—— 要么第 3 步(邀请账号)没做,要么 GA4 媒体资源 ID 填错了。两者的措辞完全一致,所以 请先核对媒体资源 ID。- 只有 Search Console 返回空 —— 多半是资源字符串与实际不符(
sc-domain:还是https://…/)。它是以空结果而非错误的形式失败的,所以请与资源选择器逐字比对。 - 保存之后凭据不见了 —— 把该字段清空后保存,就是一次显式删除。不去动它就会保留下来。
- 弹窗里出现
Sample徽章 —— 说明还没有采集到数据,因此显示的是用于呈现版式的示意样例。数据一旦到达就会自动切换为真实数据。
相关条目
- 服务 ▸ Web —— Web 监控页面本身(卡片、可用率、证书)
- 公开着陆页与访客分析 —— 采集的那一侧(gtag/GTM、Search Console 所有权验证)
- IP 信誉查询 —— 相同的只读外部查询权限模型
