# 访问统计与真实来源 IP 看板新增了基础访问统计(PV/UV)。前端在页面访问时通过 `POST /api/visit`(`navigator.sendBeacon`)上报,服务端先写入进程内缓冲、 每分钟批量落库到 `visit` 表(迁移 v7)。`/system` 系统状态页展示近 30 天 PV/UV 与 Top 页面。 统计按客户端 IP 去重,因此**必须在反代层还原真实客户端 IP**。 ## 链路与 socket 对端 ```text 浏览器 → 域名 → CDN(Cloudflare/EdgeOne) → 公网 EIP → 10.10.8.x(云服务器内网) → 宿主机 nginx → proxy_pass http://127.0.0.1:8080 → 容器 ``` 容器内 socket 对端经端口映射 SNAT 后是 Docker 网关(如 `172.17.0.1`), 不是真实客户端。因此真实 IP 只能来自 nginx 注入的请求头,且有三条来源: | 场景 | nginx 处理 | |---|---| | 域名直接解析到公网 EIP | `$remote_addr` 即客户端,nginx 直接写 `X-Real-IP` | | 经 Cloudflare | `realip` + `CF-Connecting-IP`(仅当对端属于 CF 网段才采信) | | 经 EdgeOne | `realip` + `EO-Connecting-IP`(仅当对端属于 EdgeOne 网段才采信) | 应用只读 `X-Real-IP`,且**仅当 socket 对端属于可信代理网段** (默认回环 + RFC1918 + Docker 网段,可配置)时才采信,否则回退到对端。 这样直连 EIP 的请求即使伪造 `CF-Connecting-IP` / `EO-Connecting-IP` / `X-Forwarded-For` 也无法污染统计。 > 关键:用 `realip` 模块按 CDN 网段校验,而不是无条件相信 CDN 头。 > 若无条件相信,任何能直连公网 EIP 的人都能伪造来源 IP。 ## 文件 | 文件 | 用途 | |---|---| | `reaction-board.conf` | 站点 `server {}`,反代到 `127.0.0.1:8080` 并注入真实 IP 头 | | `realip-cloudflare.conf` | Cloudflare 场景,放在 `http {}` 内 | | `realip-edgeone.conf` | EdgeOne 场景,放在 `http {}` 内 | | `update-cloudflare-ips.sh` | 生成 Cloudflare 网段清单 `cloudflare-ips.conf` | | `update-edgeone-ips.sh` | 由运维维护的 CIDR 清单生成 `edgeone-ips.conf` | 按实际来源**二选一**引入对应的 `realip-*.conf`(放在 `nginx.conf` 的 `http {}` 内),再 `include` 站点配置。 ## Cloudflare ```nginx # nginx.conf 的 http {} 内 include /etc/nginx/realip-cloudflare.conf; include /etc/nginx/conf.d/reaction-board.conf; ``` ```bash sudo bash deploy/nginx/update-cloudflare-ips.sh ``` Cloudflare 的 IP 段变化很少,建议加 cron 每月刷新一次。 ## EdgeOne EdgeOne 节点网段会变动,需从 EdgeOne 控制台或 API 获取后维护成 CIDR 清单: ```bash # source 为每行一个 CIDR 的清单 sudo bash deploy/nginx/update-edgeone-ips.sh /path/to/edgeone-cidrs.txt ``` ```nginx # nginx.conf 的 http {} 内 include /etc/nginx/realip-edgeone.conf; include /etc/nginx/conf.d/reaction-board.conf; ``` 若无法自动同步网段,可退化为:在安全组/防火墙只放行 EdgeOne 节点访问源站, 此时可放宽为直接采信 `EO-Connecting-IP`;但**同时保留直连公网 EIP 访问**的 场景下,仍建议使用 `realip` 网段校验。 ## 应用侧配置 ```jsonc { "web": { "track": { "enabled": true, "header_order": ["X-Real-IP", "X-Forwarded-For"], "trusted_proxies": [ "127.0.0.0/8", "::1/128", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "fc00::/7", "fe80::/10" ], "count_bots": false, "flush_seconds": 60, "keep_days": 180, "max_hits_per_ip_path_per_day": 0 } } } ``` - `flush_seconds`:内存缓冲落库周期;设为 `0` 可关闭后台刷新(仅调试用)。 - `count_bots`:是否统计命中常见爬虫/监控 UA 的请求,默认过滤。 - `max_hits_per_ip_path_per_day`:单个(IP, 页面, 日期)计数上限,`0` 表示不限。 - `keep_days`:保留天数,后台按日清理更早的记录。