fix: 访问统计口径与体验——PV只计页面级接口(过滤轮询,今日221→真实口径);埋点记录userId并显示昵称;访问日志Tab合并用户列

This commit is contained in:
Hermes Agent
2026-09-19 16:59:04 +08:00
parent 49946c6d21
commit 5f3809831f
3 changed files with 40 additions and 22 deletions
+32 -16
View File
@@ -66,25 +66,41 @@ const limiter = rateLimit({
})
app.use('/api/', limiter)
// 访问埋点:记录页面级 GET 请求(排除管理后台/认证/健康检查),小程序与 H5 访问均可统计
// 访问埋点:记录页面级 GET 请求(排除管理后台/认证/健康检查/高频轮询接口),小程序与 H5 访问均可统计
// PV 口径:只记"页面首屏"接口(每打开一页只计1次),轮询/统计类接口不计——避免一次访问刷出6-8条
const TRACKABLE_PAGES = [/^\/knowledge(\?|$)/, /^\/trend(\?|$)/, /^\/knowledge\/stats/, /^\/ai-chat\/quota/, /^\/user\/leaderboard/, /^\/feedback/]
app.use('/api', (req, res, next) => {
try {
if (req.method === 'GET' && !req.path.startsWith('/admin') && !req.path.startsWith('/auth')) {
const ua = String(req.headers['user-agent'] || '')
const referer = String(req.headers.referer || '')
let source = '直接访问'
if (referer.includes('servicewechat.com')) source = '小程序'
else if (referer.includes('/h5') || referer.includes('wdkj.yuzhiran.com.cn')) source = 'H5'
const os = /windows/i.test(ua) ? 'Windows' : /iphone|ipad|ios/i.test(ua) ? 'iOS' : /android/i.test(ua) ? 'Android' : /mac os/i.test(ua) ? 'MacOS' : ''
const browser = /MicroMessenger/i.test(ua) ? '微信' : /Mobile Safari/i.test(ua) ? 'Safari' : /Edg\//i.test(ua) ? 'Edge' : /Chrome/i.test(ua) ? 'Chrome' : /Firefox/i.test(ua) ? 'Firefox' : ''
const device = /Mobile|iPhone/i.test(ua) || (/Android/i.test(ua) && /Mobile/i.test(ua)) ? '移动端' : '桌面端'
VisitLog.create({
path: String(req.originalUrl || req.path).slice(0, 120),
referer: String(req.headers.referer || '').slice(0, 200),
source,
ip: req.ip || '',
os, browser, device,
}).catch(() => {})
const path = String(req.originalUrl || req.path).split('?')[0]
const isPage = TRACKABLE_PAGES.some((re) => re.test(path))
if (isPage) {
const ua = String(req.headers['user-agent'] || '')
const referer = String(req.headers.referer || '')
let source = '直接访问'
if (referer.includes('servicewechat.com')) source = '小程序'
else if (referer.includes('/h5') || referer.includes('wdkj.yuzhiran.com.cn')) source = 'H5'
const os = /windows/i.test(ua) ? 'Windows' : /iphone|ipad|ios/i.test(ua) ? 'iOS' : /android/i.test(ua) ? 'Android' : /mac os/i.test(ua) ? 'MacOS' : ''
const browser = /MicroMessenger/i.test(ua) ? '微信' : /Mobile Safari/i.test(ua) ? 'Safari' : /Edge\//i.test(ua) ? 'Edge' : /Chrome/i.test(ua) ? 'Chrome' : /Firefox/i.test(ua) ? 'Firefox' : ''
const device = /Mobile|iPhone/i.test(ua) || (/Android/i.test(ua) && /Mobile/i.test(ua)) ? '移动端' : '桌面端'
// 解析登录用户(不强制,游客为空)
let userId = null
const auth = String(req.headers.authorization || '')
if (auth.startsWith('Bearer ')) {
try {
const decoded = require('jsonwebtoken').verify(auth.slice(7), process.env.JWT_SECRET)
userId = decoded.userId || null
} catch { /* token 无效视为游客 */ }
}
VisitLog.create({
path: String(req.originalUrl || req.path).slice(0, 120),
referer: String(req.headers.referer || '').slice(0, 200),
source,
ip: req.ip || '',
userId,
os, browser, device,
}).catch(() => {})
}
}
} catch (e) { /* 埋点失败不影响业务 */ }
next()