fix:
This commit is contained in:
13
server/db.js
13
server/db.js
@@ -236,6 +236,19 @@ function runMigrations(db) {
|
||||
INSERT OR IGNORE INTO share_count (id, total) VALUES (1, 0);
|
||||
`)
|
||||
} catch (_) {}
|
||||
try {
|
||||
exec(`
|
||||
CREATE TABLE IF NOT EXISTS display_stats (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
viewers INTEGER NULL,
|
||||
cumulative INTEGER NULL,
|
||||
share_count INTEGER NULL,
|
||||
like_count INTEGER NULL,
|
||||
feedback_count INTEGER NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO display_stats (id) VALUES (1);
|
||||
`)
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async function initDb() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
const db = require('./db')
|
||||
|
||||
function toNum(v) {
|
||||
if (v == null || v === '') return 0
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0
|
||||
}
|
||||
|
||||
function getStats() {
|
||||
const viewers = db.prepare(
|
||||
const viewersRow = db.prepare(
|
||||
"SELECT COUNT(*) as n FROM visits WHERE last_seen > datetime('now', '-2 minutes')"
|
||||
).get().n
|
||||
const cumulative = db.prepare('SELECT total FROM visitor_count WHERE id = 1').get()?.total ?? 0
|
||||
const feedbackCount = db.prepare('SELECT COUNT(*) as n FROM feedback').get().n ?? 0
|
||||
const shareCount = db.prepare('SELECT total FROM share_count WHERE id = 1').get()?.total ?? 0
|
||||
return { viewers, cumulative, feedbackCount, shareCount }
|
||||
).get()
|
||||
const cumulativeRow = db.prepare('SELECT total FROM visitor_count WHERE id = 1').get()
|
||||
const feedbackRow = db.prepare('SELECT COUNT(*) as n FROM feedback').get()
|
||||
const shareRow = db.prepare('SELECT total FROM share_count WHERE id = 1').get()
|
||||
let viewers = toNum(viewersRow?.n)
|
||||
let cumulative = toNum(cumulativeRow?.total)
|
||||
let feedbackCount = toNum(feedbackRow?.n)
|
||||
let shareCount = toNum(shareRow?.total)
|
||||
let likeCount = 0
|
||||
let display = null
|
||||
try {
|
||||
display = db.prepare('SELECT viewers, cumulative, share_count, like_count, feedback_count FROM display_stats WHERE id = 1').get()
|
||||
} catch (_) {}
|
||||
if (display) {
|
||||
if (display.viewers != null) viewers = toNum(display.viewers)
|
||||
if (display.cumulative != null) cumulative = toNum(display.cumulative)
|
||||
if (display.share_count != null) shareCount = toNum(display.share_count)
|
||||
if (display.like_count != null) likeCount = toNum(display.like_count)
|
||||
if (display.feedback_count != null) feedbackCount = toNum(display.feedback_count)
|
||||
}
|
||||
return { viewers, cumulative, feedbackCount, shareCount, likeCount }
|
||||
}
|
||||
|
||||
module.exports = { getStats }
|
||||
|
||||
Reference in New Issue
Block a user