更新首页
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
const fs = require('fs')
|
||||
const content = `import cron from 'node-cron'
|
||||
import { prisma } from './db.js'
|
||||
import mysql from 'mysql2/promise'
|
||||
import { fetchNewsList, persistNews } from '../scripts/fetchers/news.fetcher.js'
|
||||
import { checkAfterRefresh } from './alert-engine.js'
|
||||
|
||||
function parseUrl(url) {
|
||||
const u = new URL(url)
|
||||
return { host: u.hostname, port: Number(u.port) || 3306, user: decodeURIComponent(u.username), password: decodeURIComponent(u.password), database: u.pathname.replace(/^\\//, '') }
|
||||
}
|
||||
|
||||
async function openConn() {
|
||||
return mysql.createConnection(parseUrl(process.env.DATABASE_URL))
|
||||
}
|
||||
|
||||
// 每天凌晨 3:00 (本地时间) 拉取新闻 + 跑跨源对比
|
||||
function scheduleNews() {
|
||||
cron.schedule('0 3 * * *', async () => {
|
||||
console.log('[cron] 3:00 触发')
|
||||
try {
|
||||
const list = await fetchNewsList()
|
||||
const r = await persistNews(list)
|
||||
console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped)
|
||||
const conn = await openConn()
|
||||
try {
|
||||
const a = await checkAfterRefresh(conn)
|
||||
console.log('[cron] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')')
|
||||
} finally { await conn.end() }
|
||||
} catch (e) {
|
||||
console.error('[cron] 任务失败:', e.message)
|
||||
}
|
||||
})
|
||||
console.log('[cron] 已注册: 每天 03:00 拉取新闻 + 跨源报警检测')
|
||||
}
|
||||
|
||||
// 每 2 分钟: 拉取 komari 节点 + 服务自检
|
||||
function scheduleMonitors() {
|
||||
cron.schedule('*/2 * * * *', async () => {
|
||||
try {
|
||||
const k = require('./komari-monitor.cjs')
|
||||
const r = await k.runOnce()
|
||||
console.log('[cron] komari: nodes=' + r.nodes + ' snapshots=' + r.snapshots + ' history=' + r.history + ' failed=' + r.failed)
|
||||
} catch (e) { console.error('[cron] komari 任务失败:', e.message) }
|
||||
try {
|
||||
const h = require('./health-check.cjs')
|
||||
const arr = await h.runOnce()
|
||||
const down = arr.filter(x => x.status !== 'up').map(x => x.name)
|
||||
console.log('[cron] health: ' + arr.length + ' 项, down=' + (down.length ? down.join(',') : '无'))
|
||||
} catch (e) { console.error('[cron] health 任务失败:', e.message) }
|
||||
})
|
||||
console.log('[cron] 已注册: 每 2 分钟 komari + 服务自检')
|
||||
}
|
||||
|
||||
// 每小时: 清理 24h 前的 komari 历史
|
||||
function scheduleMonitorCleanup() {
|
||||
cron.schedule('0 * * * *', async () => {
|
||||
try {
|
||||
const k = require('./komari-monitor.cjs')
|
||||
const n = await k.cleanupOld()
|
||||
console.log('[cron] 监控历史清理: 删除 ' + n + ' 条')
|
||||
} catch (e) { console.error('[cron] 监控清理失败:', e.message) }
|
||||
})
|
||||
console.log('[cron] 已注册: 每小时清理 24h 前监控历史')
|
||||
}
|
||||
|
||||
scheduleNews()
|
||||
scheduleMonitors()
|
||||
scheduleMonitorCleanup()
|
||||
`
|
||||
fs.writeFileSync('server/cron.js', content, 'utf8')
|
||||
console.log('OK len=' + content.length)
|
||||
@@ -50,15 +50,17 @@ function parseNewsSheet(sheet, impactBase = 0) {
|
||||
const out = []
|
||||
for (let i = 0; i < sheet.items.length; i++) {
|
||||
const r = sheet.items[i]
|
||||
const title = String(r[0] || '').trim()
|
||||
const title = String(r[0] || '').trim().slice(0, 190)
|
||||
if (!title) continue
|
||||
const publishedAt = parsePublishedAt(r[2])
|
||||
if (!publishedAt) continue
|
||||
const body = String(r[1] || '').trim()
|
||||
out.push({
|
||||
title,
|
||||
summary: String(r[1] || '').trim().slice(0, 180),
|
||||
summary: body.slice(0, 180),
|
||||
content: body.slice(0, 8000), // 正文全文 (MCP "摘要"列实际是正文), 上限防异常超长
|
||||
source: String(r[3] || '').trim().slice(0, 80),
|
||||
url: String(r[4] || '').trim().slice(0, 400),
|
||||
url: String(r[4] || '').trim().slice(0, 190), // 列宽 varchar(191), 超长会被 MySQL 拒绝
|
||||
publishedAt,
|
||||
impactRank: impactBase + i + 1,
|
||||
})
|
||||
@@ -103,20 +105,27 @@ export async function persistNews(newsList) {
|
||||
if (!process.env.DATABASE_URL) throw new Error('缺少 DATABASE_URL')
|
||||
if (!newsList || newsList.length === 0) return { inserted: 0, skipped: 0 }
|
||||
const conn = await mysql.createConnection(parseUrl(process.env.DATABASE_URL))
|
||||
let inserted = 0, skipped = 0
|
||||
let inserted = 0, skipped = 0, failed = 0
|
||||
try {
|
||||
for (const n of newsList) {
|
||||
const [r] = await conn.execute(
|
||||
'INSERT INTO `News` (`title`, `summary`, `source`, `url`, `publishedAt`, `impactRank`, `createdAt`) ' +
|
||||
'VALUES (?, ?, ?, ?, ?, ?, NOW(3)) ' +
|
||||
'ON DUPLICATE KEY UPDATE `impactRank` = VALUES(`impactRank`)',
|
||||
[n.title, n.summary, n.source, n.url, n.publishedAt, n.impactRank],
|
||||
)
|
||||
if (r.affectedRows === 1) inserted++
|
||||
else skipped++
|
||||
try {
|
||||
// 注意: content 列依赖迁移 add_news_content 已应用, 发布顺序须先迁移后跑本脚本
|
||||
// 重复命中 (title, publishedAt) 时只回填仍为 NULL 的 content, 避免覆盖已有全文
|
||||
const [r] = await conn.execute(
|
||||
'INSERT INTO `News` (`title`, `summary`, `content`, `source`, `url`, `publishedAt`, `impactRank`, `createdAt`) ' +
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?, NOW(3)) ' +
|
||||
'AS new ON DUPLICATE KEY UPDATE `impactRank` = new.`impactRank`, `content` = IFNULL(News.`content`, new.`content`)',
|
||||
[n.title, n.summary, n.content || null, n.source, n.url, n.publishedAt, n.impactRank],
|
||||
)
|
||||
if (r.affectedRows === 1) inserted++
|
||||
else skipped++
|
||||
} catch (e) {
|
||||
failed++
|
||||
console.error('[News] 单条入库失败:', n.title, e.message)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await conn.end()
|
||||
}
|
||||
return { inserted, skipped }
|
||||
return { inserted, skipped, failed }
|
||||
}
|
||||
@@ -7,6 +7,6 @@ async function main() {
|
||||
console.log('[News] 拉取 ' + list.length + ' 条, 耗时 ' + ((Date.now() - t0) / 1000).toFixed(1) + 's')
|
||||
if (list.length === 0) { console.log('[News] 无数据, 跳过'); return }
|
||||
const r = await mod.persistNews(list)
|
||||
console.log('[News] 入库: 新增 ' + r.inserted + ' 条, 重复跳过 ' + r.skipped + ' 条')
|
||||
console.log('[News] 入库: 新增 ' + r.inserted + ' 条, 重复跳过 ' + r.skipped + ' 条' + (r.failed ? ', 失败 ' + r.failed + ' 条' : ''))
|
||||
}
|
||||
main().catch((e) => { console.error('[News] 失败:', e); process.exit(1) })
|
||||
|
||||
Reference in New Issue
Block a user