This commit is contained in:
Daniel
2026-03-04 00:07:14 +08:00
parent ac24c528f3
commit 95e2fe1c41
7 changed files with 271 additions and 45 deletions

View File

@@ -105,12 +105,12 @@ function getClientIp(req) {
return req.ip || req.socket?.remoteAddress || 'unknown'
}
// 优先用前端传来的 viewer-id 去重(每设备一个),否则用 IP这样多设备同 WiFi 也能正确统计「在看」
function getVisitKey(req) {
const vid = req.headers['x-viewer-id']
const ip = getClientIp(req)
const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1'
if (typeof vid === 'string' && vid.trim().length > 0 && (process.env.NODE_ENV === 'development' || isLocal)) {
return 'vid:' + vid.trim()
if (typeof vid === 'string' && vid.trim().length > 0) {
return 'vid:' + vid.trim().slice(0, 64)
}
return ip
}
@@ -129,7 +129,7 @@ router.post('/visit', (req, res) => {
res.json(getStats())
} catch (err) {
console.error(err)
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0 })
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0, likeCount: 0 })
}
})
@@ -168,7 +168,7 @@ router.get('/stats', (req, res) => {
res.json(getStats())
} catch (err) {
console.error(err)
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0 })
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0, likeCount: 0 })
}
})
@@ -205,11 +205,28 @@ router.get('/edit/raw', (req, res) => {
const updates = db.prepare('SELECT id, timestamp, category, summary, severity FROM situation_update ORDER BY timestamp DESC LIMIT 80').all()
const summaryUs = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('us')
const summaryIr = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('iran')
let displayStats = null
try {
displayStats = db.prepare('SELECT viewers, cumulative, share_count, like_count, feedback_count FROM display_stats WHERE id = 1').get()
} catch (_) {}
const realCumulative = db.prepare('SELECT total FROM visitor_count WHERE id = 1').get()?.total ?? 0
const realShare = db.prepare('SELECT total FROM share_count WHERE id = 1').get()?.total ?? 0
const liveViewers = db.prepare(
"SELECT COUNT(*) as n FROM visits WHERE last_seen > datetime('now', '-2 minutes')"
).get()?.n ?? 0
const realFeedback = db.prepare('SELECT COUNT(*) as n FROM feedback').get()?.n ?? 0
res.json({
combatLosses: { us: lossesUs || null, iran: lossesIr || null },
keyLocations: { us: locUs || [], iran: locIr || [] },
situationUpdates: updates || [],
forceSummary: { us: summaryUs || null, iran: summaryIr || null },
displayStats: {
viewers: displayStats?.viewers ?? liveViewers,
cumulative: displayStats?.cumulative ?? realCumulative,
shareCount: displayStats?.share_count ?? realShare,
likeCount: displayStats?.like_count ?? 0,
feedbackCount: displayStats?.feedback_count ?? realFeedback,
},
})
} catch (err) {
console.error(err)
@@ -343,4 +360,39 @@ router.put('/edit/force-summary', (req, res) => {
}
})
/** PUT 更新展示统计(看过、在看、分享、点赞、留言)。传 null 表示清除覆盖、改用实时统计 */
router.put('/edit/display-stats', (req, res) => {
try {
db.prepare('INSERT OR IGNORE INTO display_stats (id) VALUES (1)').run()
const updates = []
const values = []
const setField = (key, bodyKey) => {
const v = req.body?.[bodyKey ?? key]
if (v === undefined) return
if (v === null) {
updates.push(`${key} = ?`)
values.push(null)
return
}
const n = Math.max(0, parseInt(v, 10))
if (!Number.isFinite(n)) throw new Error(`${bodyKey ?? key} must be number`)
updates.push(`${key} = ?`)
values.push(n)
}
setField('viewers')
setField('cumulative')
setField('share_count', 'shareCount')
setField('like_count', 'likeCount')
setField('feedback_count', 'feedbackCount')
if (updates.length === 0) return res.status(400).json({ error: 'no fields to update' })
values.push(1)
db.prepare(`UPDATE display_stats SET ${updates.join(', ')} WHERE id = ?`).run(...values)
broadcastAfterEdit(req)
res.json({ ok: true })
} catch (err) {
console.error(err)
res.status(400).json({ error: err.message })
}
})
module.exports = router