feat(site): 方案 C - 「我常访问」个人榜(匿名 ID + localStorage 合并)

- NavClick 加 anon_id 字段(已为历史表自动迁移)
- 后端静默下发 wk_anon cookie(uuid4,1 年,HttpOnly)
- 点击埋点写入 anon_id;新增 GET /site/api/my-top 与 POST /site/api/my-clear
- 首页服务端渲染「我常访问」区(时间衰减加权分),与「热门网址」共显
- 前端 localStorage 累计 + 与服务端榜取 max 合并;冷启动阈值 2 个网址
- 一键清除:清 localStorage + 服务端删该匿名 ID 的全部记录
- 移动端抽屉、顶部导航同步加「我常访问」入口
- 底部隐私说明更新
This commit is contained in:
bwstudio
2026-09-06 19:16:45 +08:00
parent a47ad991d0
commit 34e8010830
7 changed files with 521 additions and 62 deletions
+63
View File
@@ -0,0 +1,63 @@
"""验证「我常访问」:服务端匿名榜 + localStorage 合并 + 清除按钮。
用法:先启动 Flaskpython app.py),再 PYTHONPATH=. python scripts/verify_mine_section.py
"""
from playwright.sync_api import sync_playwright
BASE = 'http://127.0.0.1:5000'
LS_KEY = 'wk_mine_clicks'
with sync_playwright() as p:
browser = p.chromium.launch()
ctx = browser.new_context(viewport={'width': 1280, 'height': 900})
page = ctx.new_page()
# ---- 1) 全新访客:没有点击记录,不该出现个人区 ----
page.goto(BASE + '/site/', wait_until='networkidle')
print('1) 新访客 #cat-mine 存在:', page.locator('#cat-mine').count())
anon = [c for c in ctx.cookies() if c['name'] == 'wk_anon']
print(' 匿名 cookie:', (anon[0]['value'][:8] + '...') if anon else '')
# 取页面上真实存在的几个 nav id
ids = page.eval_on_selector_all(
'.section:not(#cat-mine) .card[data-nav-id]',
'els => els.slice(0, 5).map(e => e.getAttribute("data-nav-id"))'
)
print(' 可用 nav id:', ids)
# ---- 2) 只写 localStorage(模拟 cookie 被禁 / 服务端还没同步)----
page.evaluate(
"""([k, a, b]) => localStorage.setItem(k, JSON.stringify({[a]: 4, [b]: 2}))""",
[LS_KEY, ids[0], ids[1]]
)
page.reload(wait_until='networkidle')
n = page.locator('#cat-mine .card').count()
print('2) localStorage 驱动后 #cat-mine 卡片数:', n)
print(' 顶部导航入口:', page.locator('.cat-nav-mine').count(),
'| 抽屉入口:', page.locator('.cat-drawer-mine').count())
if n:
print(' 首卡文案:', page.locator('#cat-mine .card').first.inner_text().replace('\n', ' | ')[:90])
# ---- 3) 只点 1 个网址:冷启动阈值,不该展示 ----
page.evaluate("k => localStorage.setItem(k, JSON.stringify({}))", LS_KEY)
page.reload(wait_until='networkidle')
print('3) 清空 localStorage 后 #cat-mine 可见:',
page.locator('#cat-mine').count() and page.locator('#cat-mine').is_visible())
# ---- 4) 清除按钮 ----
page.evaluate(
"""([k, a, b]) => localStorage.setItem(k, JSON.stringify({[a]: 3, [b]: 1}))""",
[LS_KEY, ids[0], ids[1]]
)
page.reload(wait_until='networkidle')
print('4) 清除前 #cat-mine 数:', page.locator('#cat-mine').count())
clears = []
page.on('request', lambda r: clears.append(r.url) if '/site/api/my-clear' in r.url else None)
page.locator('#clear-mine').first.click()
page.wait_for_timeout(600)
print(' 清除后 #cat-mine 数:', page.locator('#cat-mine').count(),
'| localStorage:', page.evaluate("k => localStorage.getItem(k)", LS_KEY),
'| 请求:', clears)
browser.close()
print('DONE')