feat(stats): buffer visits in memory and expose POST /api/visit + /api/stats

This commit is contained in:
kairee committed 2026-11-01 15:34:53 +08:00
1 parent cdd11823b9
commit 3c16caac1a
8 files changed
+905 -5

No files matched your search

+70 -1
View File
@@ -172,13 +172,82 @@ def test_analytics_defaults_and_validation():
raise AssertionError(f"非法 analytics 必须拒绝: {bad}")
def test_web_track_defaults_and_validation():
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "config.json"
write(path, base_config())
cfg = load_config(path, env={})
track = cfg.web.track
assert track.enabled is True
assert track.header_order == ("X-Real-IP", "X-Forwarded-For")
assert "172.16.0.0/12" in track.trusted_proxies
assert track.count_bots is False
assert track.flush_seconds == 60
assert track.keep_days == 180
assert track.max_hits_per_ip_path_per_day == 0
data = base_config()
data["web"] = {
"host": "0.0.0.0", "port": 8080, "auth_token": "t",
"public_base_url": "",
"track": {
"enabled": False,
"header_order": ["X-Forwarded-For", "X-Real-IP", "X-Real-IP"],
"trusted_proxies": ["10.0.0.0/8", "192.168.1.5"],
"count_bots": True,
"flush_seconds": 0,
"keep_days": 30,
"max_hits_per_ip_path_per_day": 500,
},
}
write(path, data)
cfg = load_config(path, env={})
assert cfg.web.track.enabled is False
# 去重且保持顺序
assert cfg.web.track.header_order == ("X-Forwarded-For", "X-Real-IP")
assert cfg.web.track.trusted_proxies == ("10.0.0.0/8", "192.168.1.5/32")
assert cfg.web.track.count_bots is True
assert cfg.web.track.flush_seconds == 0
assert cfg.web.track.max_hits_per_ip_path_per_day == 500
for bad, hint in (
({"header_order": []}, "header_order"),
({"header_order": ["X-Real-IP", 5]}, "header_order"),
({"trusted_proxies": ["not-a-cidr"]}, "trusted_proxies"),
({"flush_seconds": -1}, "flush_seconds"),
({"keep_days": 0}, "keep_days"),
({"max_hits_per_ip_path_per_day": -1}, "max_hits_per_ip_path_per_day"),
):
data = base_config()
data["web"] = {"track": bad}
write(path, data)
try:
load_config(path, env={})
except ConfigError as exc:
assert hint in str(exc), (bad, str(exc))
else:
raise AssertionError(f"非法 web.track 必须拒绝: {bad}")
# web.track 不是对象
data = base_config()
data["web"] = {"track": "nope"}
write(path, data)
try:
load_config(path, env={})
except ConfigError as exc:
assert "web.track" in str(exc)
else:
raise AssertionError("web.track 必须是对象")
def main():
test_dual_accounts_and_env_override()
test_flat_and_missing_account_rejected()
test_validation_require_read_auth_and_hot_reload()
test_invalid_values()
test_analytics_defaults_and_validation()
print("OK: 双账号配置加载、拒绝 flat 格式、ENV 覆盖、热加载与 analytics 测试全部通过")
test_web_track_defaults_and_validation()
print("OK: 双账号配置加载、拒绝 flat 格式、ENV 覆盖、热加载与 analytics/web.track 测试全部通过")
if __name__ == "__main__":