本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/site_stats插件功能增强建议_2026-09-24.md。
site_stats 插件功能增强建议(v2)
- 日期:2026-09-24
- 配套文档:
site_stats插件审计报告_2026-09-24.md(2 项 P0) - v2 修订:① 修正「加列」的表述(实测后确认比 v1 说的简单得多);② 功能建议从 18 项扩充到 34 项,并从「按技术分层」改为「按运营场景」组织。
一、先回答:加列只靠幂等行不行?
行,但 SQLite 有个坑。实测(SQLite 3.39.2,内存库):
| 方案 | 实测结果 |
|---|---|
ALTER TABLE t ADD COLUMN IF NOT EXISTS x | ❌ 语法错误 near "EXISTS" —— SQLite 不支持(MySQL/MariaDB 才有) |
裸 ADD COLUMN 重复执行 | ❌ duplicate column name: ref_host |
PRAGMA table_info 先检测再 ALTER | ✅ 真幂等(第 2 次不再执行 ALTER,无异常) |
try/catch 吞掉 duplicate 错误 | ✅ 也可行(但把真实错误一起吞了,不如前者干净) |
所以正确写法就是 3 行:
$cols = array_column($pdo->query('PRAGMA table_info(ss_views)')->fetchAll(PDO::FETCH_ASSOC), 'name');
if (!in_array('ref_host', $cols, true)) {
$pdo->exec("ALTER TABLE ss_views ADD COLUMN ref_host TEXT NOT NULL DEFAULT ''");
}另外实测确认两件事,说明加列风险比我 v1 说的低得多:
- 加列向后兼容:加完列后,老 INSERT 语句(不含新列)照样成功,新列自动取
DEFAULT。
→ 不需要「加列和改代码必须原子上线」。
- 回填也是幂等的:
UPDATE ... WHERE ref_host = ''第 1 次影响 2 行、第 2 次影响 0 行。
→ 重复跑不会重复劳动。
修正 v1 的表述:v1 写「加列必须写迁移,否则存量库静默不加列」—— 这句本身没错
(
ensureSchema只跑CREATE TABLE IF NOT EXISTS,确实不补列),但说重了,听起来像大工程。实际就是上面 3 行检测 + 1 句 ALTER。
真正需要「版本号门控」的只有回填:回填虽幂等,但每次请求都跑一次全表 UPDATE 是浪费
(10 万行约几十 ms,且写在每页触发的路径上会撞 §11.4 的查询预算)。
→ 只在
SCHEMA_VERSION变化时跑一次。这才是 P2-1 版本号的价值所在。
二、再回答:功能不止外链
v1 其实列了 18 项,但外链那条太显眼,把别的盖住了。这版重新组织成 5 个运营场景、34 项,
并按「你每天真正会看什么」排优先级。
三、现状盘点:一半字段是「白采集」
| 字段 | 采集率 | 被统计使用 | 结论 |
|---|---|---|---|
path / created_at / ip_hash | 100% | ✅ | 在用 |
user_id | 100% | ❌ 仅 INSERT | 白采集 |
ua | 100% | ❌ 仅 INSERT | 白采集 |
referer | 14/15(93%) | ❌ 仅 INSERT | 白采集 |
getStats() 从头到尾没查过 referer、ua、user_id。
⚠️ 同一站点实测有
p.zgzjw.top+localhost两个访问入口 → 内链判定需要「本站域名白名单」,否则站内跳转会被误判成外链。
四、referer 的能力边界(决定哪些是画饼)
| 场景 | 浏览器实际发送 | 能做吗 |
|---|---|---|
| 同源跳转 | 完整 URL 含 query | ✅ 最完整(实测 ?q=统计) |
| 跨域跳转 | 只有 origin(strict-origin-when-cross-origin) | ✅ 域名可做,路径/关键词不可 |
| 直接访问 / 书签 / App | 空 | ⚠️ 归入「直接访问」 |
结论:
- ✅ 来源域名完全可做
- ⚠️ 跨域搜索关键词做不到(百度/Google 早已剥离 query)→ 别设计这个字段
- ✅ 站内搜索词可做(同源带 query)
五、功能建议(按运营场景,34 项)
场景 1|站点健康度 —— 一眼知道在涨还是在跌
| # | 功能 | 说明 | 代价 |
|---|---|---|---|
| 1 | 环比对比 | 今天 vs 昨天、本周 vs 上周,带涨跌箭头/百分比 | 极低(纯 SQL) |
| 2 | 异常提示 | 流量较基准骤降/骤升时高亮 | 低 |
| 3 | 概览卡增强 | 现有 6 个卡片加一行环比小字 | 极低 |
⚠️ 这是全清单性价比最高的一项:现在只有绝对值,站长看不出站点在涨还是在跌。
纯 SQL 可实现,零表结构改动。
场景 2|流量来源
| # | 功能 | 说明 | 依赖 |
|---|---|---|---|
| 4 | 外链来源 Top N | 你点名的(referer 已采集)域名 / PV / UV | 无 |
| 5 | 来源类型分布 | 直接 / 站内 / 搜索引擎 / 社交 / 其他外链 | 无 |
| 6 | 外链落地页 | 哪个页面引流最多 | 无 |
| 7 | 爬虫访问统计 | ⚠️ 现在爬虫被直接丢弃(route_after_dispatch.php:36 直接 return)→ SEO 视角的抓取频率数据白白浪费 | 加列 is_bot |
| 8 | 搜索引擎细分 | 百度 / Google / 必应 各自多少 | 无 |
| 9 | 站内搜索词 | 用户在站内搜什么(同源 referer 带 ?q=) | 无 |
| 10 | 站内跳转路径 | 站内 A → B 的流转(同源 referer 完整可用) | 无 |
场景 3|内容运营
| # | 功能 | 说明 | 依赖 |
|---|---|---|---|
| 11 | 热门页面 | 已有 | — |
| 12 | 单页 UV 排行 | 现在只有 PV 排序,UV 维度更有意义 | 无 |
| 13 | 高浏览低回复帖 | 内容诊断:有人看没人聊 → 需要读 topic_index(必须走 Model,§10.1) | 跨库读 |
| 14 | 版块活跃对比 | 从 path 解析版块 ID,看哪个版块在衰退 | 加列 entity_id |
| 15 | 内容衰减 | 老帖访问量随时间变化 | 无 |
| 16 | 内容类型对比 | 帖子 / 博客 / 问答 的流量占比 | path 解析 |
场景 4|用户运营
| # | 功能 | 说明 | 依赖 |
|---|---|---|---|
| 17 | 会员 vs 游客 | 登录用户占比(user_id 已有) | 无 |
| 18 | 新老访客比 | ip_hash 首次出现时间 | 无 |
| 19 | 时段活跃分布 | 24 根柱,定位活跃时段 | 无 |
| 20 | 星期 × 小时热力图 | 24×7 矩阵,视觉效果好 | 无 |
| 21 | 留存曲线 | D1 / D7 / D30 | 无(近似) |
| 22 | 设备分布 | PC / 移动 / 平板 | 无(SQL LIKE) |
| 23 | 浏览器 / OS 分布 | 同上 | 加列 device 更准 |
| 24 | 访问深度 | 单次会话访问了几个页面 | 需 C2 会话表 |
| 25 | 回访周期 | 用户平均多久回来一次 | 无 |
| 26 | 沉默用户 | 注册后 N 天未回访(跨库读 users.created_at) | 跨库读 |
场景 5|技术健康
| # | 功能 | 说明 | 依赖 |
|---|---|---|---|
| 27 | 慢页面排行 | microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'] 在 route_after_dispatch 可拿到 | 加列 elapsed_ms |
| 28 | 404 死链排行 | 哪些不存在的 URL 被访问(排查失效链接) | 加列 status |
| 29 | 接口错误率 | 5xx 占比 | 加列 status |
| 30 | 异常流量识别 | 单 IP 短时高频(核心已有 RateLimiter,这里是事后统计) | 无 |
| 31 | 静态资源命中 | 现在被排除了,可单独统计 CDN 效果 | 无 |
场景 6|数据出口
| # | 功能 | 说明 |
|---|---|---|
| 32 | CSV 导出 | 一键导出原始明细 |
| 33 | 保留策略可视化 | 当前行数 / 最早记录 / 预计清理量 |
| 34 | 每日摘要 | 定时推送到通知(可复用核心通知) |
六、新增列汇总
| 列 | 服务功能 | 备注 |
|---|---|---|
ref_host TEXT | 4/5/6/8 | 免去全表扫 URL 解析(见 §七 阈值) |
ref_type TEXT | 5 | 来源分类落库 |
device TEXT | 22/23 | UA 解析结果 |
status INTEGER | 28/29 | 响应码 → 404 统计 |
elapsed_ms INTEGER | 27 | 响应耗时 → 慢页面排行 |
is_bot INTEGER | 7 | 把现在丢弃的爬虫数据留下来 |
entity_type / entity_id | 14/16 | path 结构化 |
加列方式:§一 的 3 行 PRAGMA 检测,逐个加。索引用 CREATE INDEX IF NOT EXISTS(本身幂等)。
七、性能阈值(实测)
「外链来源」两种实现对比(内存库,3 次取最优):
| 数据量 | 纯 SQL 解析(无索引) | 加 `ref_host` 列 + 索引 |
|---|---|---|
| 1 万行 | 12.47 ms | 1.43 ms |
| 10 万行 | 146.42 ms | 15.12 ms |
| 50 万行 | 725.51 ms | 76.03 ms |
- < 5 万行:纯 SQL 够用 → A 级先做,零改动
- > 10 万行:必须加列(约 10 倍差距)
- 日均千级 PV → 一年约 36 万行 → 大概率要加列
另外:
getStats()现有 6 次COUNT(DISTINCT)+ 2 次GROUP BY也是全表扫,数据量上来后 C1(日表)比 A1 的优化优先级更高。
八、与本次修复的整合
| 整合点 | 说明 |
|---|---|
| 加列检测 ≡ P2-1 版本号门控 | 把 SCHEMA_VERSION 常量、加列检测、回填绑在一起:版本变了 → 重跑建表 + 补列 + 回填一次。一次设计两处受益。 |
| P2-3(XFF 可伪造)必须先修 | ip_hash 不可信 → 「来源 × UV」的 UV 失真 |
| P0-1 必须先修 | 后台页现在任何登录用户都能看,加来源分析会放大信息泄露面 |
| 加列不加剧 P2-2 | INSERT 加几列成本可忽略(主体开销是 fsync,实测 21ms) |
九、不建议做
| 不做 | 理由 |
|---|---|
| ❌ 访客地理分布 / 地图 | 需 GeoIP 库;且只存 IP 哈希,拿不到位置 |
| ❌ 跨域搜索关键词 | 浏览器策略决定拿不到(§四) |
| ❌ 前端图表库 | 无 Chromium 测试环境;CSP script-src 'self' 需放行。纯 CSS 柱状图 + SVG 够用 |
| ❌ 明文存 IP | 破坏现有隐私设计;退一步可只存 /24 |
| ❌ 实时在线人数 | 核心已有 online_users 表,别重复造 |
| ❌ 不建会话表硬算跳出率 | ss_views 粒度不够,误差大,等 C2 |
十、落地顺序(重排)
进度(2026-09-24 12:30):第 1 批 6 项全部交付并验证,见
site_stats插件审计报告_2026-09-24.md§九。第 2 批未开始。
第 1 批|与 P0 修复同批(做完停下来验)—— ✅ 已完成
- ✅ 修 P0-1 鉴权 + P0-2 CSRF + P1-1 二次验证 ← 前置
- ✅ 修 P2-3(XFF),保证 UV 可信
- ✅ 设计
SCHEMA_VERSION门控(合并 P2-1)+ 幂等加列ref_host - ✅ 环比对比(#1) ← 最便宜的体验提升
- ✅ 外链三件套(#4/#5/#6) ← 你点名的
- ✅ 修 P1-2(CRLF)
- 额外:域名白名单(解决「站内跳转被误判外链」)+ 后台 UI 改造(tab + 左侧导航)
第 2 批|把白采集的字段用起来(未开始)
- 会员 vs 游客(#17)、时段分布(#19)、新老访客(#18)、设备(#22)
- 爬虫统计(#7)—— 把现在丢弃的数据捡回来
ss_daily日表
第 3 批|进阶(未开始)
- 星期×小时热力图(#20)、留存曲线(#21)
- 404 + 慢页面(#27/#28)
ss_sessions会话表 → 跳出率 / 停留时长 / 访问深度
附:验证方式
- 来源解析正确性:SQL 提取 host 与 PHP
parse_url()逐项交叉比对(已执行,15 行全一致) - 性能阈值:内存库造数实测(1 万 / 10 万 / 50 万行),非估算
- 加列幂等:内存库实测 4 种方案 + 向后兼容验证 + 回填幂等验证
- 上线验收:外链区块需真实外链流量才能验收 —— 当前 15 行全是站内跳转,会显示为空
- 第 1 批交付后:i18n 三语键集合断言(53 键零差异)+ 零内联复扫 + 双主题渲染对比(
.ss-admin块 sha256 一致)—— 已执行,见审计报告 §9.3