Files
re-xxm/deploy/nginx

访问统计与真实来源 IP

看板新增了基础访问统计(PV/UV)。前端在页面访问时通过 POST /api/visit(navigator.sendBeacon)上报,服务端先写入进程内缓冲、 每分钟批量落库到 visit 表(迁移 v7)。/system 系统状态页展示近 30 天 PV/UV 与 Top 页面。

统计按客户端 IP 去重,因此必须在反代层还原真实客户端 IP。

链路与 socket 对端

浏览器 → 域名 → 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.conf 的 http {} 内
include /etc/nginx/realip-cloudflare.conf;
include /etc/nginx/conf.d/reaction-board.conf;
sudo bash deploy/nginx/update-cloudflare-ips.sh

Cloudflare 的 IP 段变化很少,建议加 cron 每月刷新一次。

EdgeOne

EdgeOne 节点网段会变动,需从 EdgeOne 控制台或 API 获取后维护成 CIDR 清单:

# source 为每行一个 CIDR 的清单
sudo bash deploy/nginx/update-edgeone-ips.sh /path/to/edgeone-cidrs.txt
# nginx.conf 的 http {} 内
include /etc/nginx/realip-edgeone.conf;
include /etc/nginx/conf.d/reaction-board.conf;

若无法自动同步网段,可退化为:在安全组/防火墙只放行 EdgeOne 节点访问源站, 此时可放宽为直接采信 EO-Connecting-IP;但同时保留直连公网 EIP 访问的 场景下,仍建议使用 realip 网段校验。

应用侧配置

{
  "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:保留天数,后台按日清理更早的记录。