Files
usa/server/index.js
2026-03-04 00:39:01 +08:00

92 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const http = require('http')
const path = require('path')
const fs = require('fs')
const express = require('express')
const cors = require('cors')
const { WebSocketServer } = require('ws')
const db = require('./db')
const routes = require('./routes')
const { getSituation } = require('./situationData')
const app = express()
const PORT = process.env.API_PORT || 3001
// 爬虫通知用的共享密钥API_CRAWLER_TOKEN仅在服务端与爬虫进程间传递
const CRAWLER_TOKEN = process.env.API_CRAWLER_TOKEN || ''
app.set('trust proxy', 1)
app.use(cors())
app.use(express.json())
app.use('/api', routes)
app.get('/api/health', (_, res) => res.json({ ok: true }))
app.post('/api/crawler/notify', (req, res) => {
// 若配置了 API_CRAWLER_TOKEN则要求爬虫携带 X-Crawler-Token 头
if (CRAWLER_TOKEN) {
const token = req.headers['x-crawler-token']
if (typeof token !== 'string' || token !== CRAWLER_TOKEN) {
return res.status(401).json({ error: 'unauthorized' })
}
}
notifyCrawlerUpdate()
res.json({ ok: true })
})
// 生产环境:提供前端静态文件(含修订页 /edit依赖 SPA fallback
const distPath = path.join(__dirname, '..', 'dist')
if (fs.existsSync(distPath)) {
app.use(express.static(distPath))
// 非 API/WS 的请求一律返回 index.html由前端路由处理 /、/edit、/db 等
app.get('*', (req, res, next) => {
if (!req.path.startsWith('/api') && req.path !== '/ws') {
res.sendFile(path.join(distPath, 'index.html'))
} else next()
})
} else {
console.warn('[server] dist 目录不存在,前端页面(含 /edit 修订页)不可用。请在项目根目录执行 npm run build 后再启动。')
}
const server = http.createServer(app)
const { getStats } = require('./stats')
const wss = new WebSocketServer({ server, path: '/ws' })
wss.on('connection', (ws) => {
ws.send(JSON.stringify({ type: 'situation', data: getSituation(), stats: getStats() }))
})
function broadcastSituation() {
try {
const data = JSON.stringify({ type: 'situation', data: getSituation(), stats: getStats() })
wss.clients.forEach((c) => {
if (c.readyState === 1) c.send(data)
})
} catch (_) {}
}
app.set('broadcastSituation', broadcastSituation)
setInterval(broadcastSituation, 3000)
// 供爬虫调用:先从磁盘重载 DB纳入爬虫写入再更新 updated_at 并立即广播
function notifyCrawlerUpdate() {
try {
const db = require('./db')
db.reloadFromFile()
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastSituation()
const n = db.prepare('SELECT COUNT(*) as c FROM situation_update').get().c
console.log('[crawler/notify] DB 已重载并广播situation_update 条数:', n)
} catch (e) {
console.error('[crawler/notify]', e?.message || e)
}
}
db.initDb().then(() => {
server.listen(PORT, () => {
console.log(`API + WebSocket running at http://localhost:${PORT}`)
console.log(`Swagger docs at http://localhost:${PORT}/api-docs`)
})
}).catch((err) => {
console.error('DB init failed:', err)
process.exit(1)
})