feat(宏观): ifinance 分类动态挂载为宏观 tab, 报警迁入监控, 下线油价/本地经济数据/策略研究
Docker Build and Push / build-image (push) Successful in 2m37s
Docker Build and Push / build-image (push) Successful in 2m37s
- 宏观只保留本地 新闻/经济文章, 外接 ifinance categories[] 动态成为 tab (纯前端直连, CORS/_headers, https) - 报警从宏观迁到监控第 4 个 tab (保留 Alert 模块, 供未来新增报警源) - 彻底删除油价/经济数据/策略研究: 前端组件+api、后端 apizero/data 路由、cron、fetchers、种子数据 - 新增迁移 DROP Indicator/IndicatorPoint/Strategy/OilPriceHistory; 重新生成 Prisma Client - CSP 放开 *.honor3.com (含端口通配), 避免新增子域反复重装外壳
This commit is contained in:
+52
-110
@@ -1,110 +1,52 @@
|
||||
import cron from 'node-cron'
|
||||
import { prisma } from './db.js'
|
||||
import mysql from 'mysql2/promise'
|
||||
import { checkAfterRefresh } from './alert-engine.js'
|
||||
import { ApizeroError, oil, history } from './apizero/index.js'
|
||||
|
||||
// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载,
|
||||
// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。
|
||||
async function importNewsFetcher() {
|
||||
const mod = await import('../scripts/fetchers/news.fetcher.js')
|
||||
return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews }
|
||||
}
|
||||
|
||||
async function alertError(taskName, message) {
|
||||
try {
|
||||
const { insertCronAlert } = await import('../scripts/lib/cron-alert.js')
|
||||
await insertCronAlert(taskName, message)
|
||||
} catch (e) {
|
||||
console.error('[cron] 报警写入失败(忽略):', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 每天 6:00 (Asia/Shanghai) 拉取新闻 + 跑跨源对比, 出错生成报警
|
||||
function scheduleNews() {
|
||||
cron.schedule('0 6 * * *', async () => {
|
||||
console.log('[cron] 6:00 触发')
|
||||
try {
|
||||
const { fetchNewsList, persistNews } = await importNewsFetcher()
|
||||
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)
|
||||
await alertError('新闻拉取+报警检测', e.message)
|
||||
}
|
||||
}, { timezone: 'Asia/Shanghai' })
|
||||
console.log('[cron] 已注册: 每天 06:00 拉取新闻 + 跨源报警检测')
|
||||
}
|
||||
|
||||
// 每天 6:10 (Asia/Shanghai) 抓浙江油价历史, 出错生成报警
|
||||
// 逻辑: 拉今年 schedule, 检查待定且 <= 昨天的调价日, 命中就抓 price?province=浙江 存库
|
||||
// - 0 命中: 不调 apizero (省配额)
|
||||
// - >0 命中: 调一次, 4 个油品批量入库 (unique 兜底去重)
|
||||
function scheduleOilHistory() {
|
||||
cron.schedule('10 6 * * *', async () => {
|
||||
console.log('[cron] 6:10 油价历史触发')
|
||||
try {
|
||||
const year = new Date().getFullYear()
|
||||
const sched = await oil.getSchedule(year)
|
||||
const list = sched.schedule || []
|
||||
// 待定 且 日期 <= 昨天 (用 Asia/Shanghai 时区推导, 与 cron 触发时区对齐)
|
||||
const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' })
|
||||
const today = new Date(todayStr + 'T00:00:00+08:00')
|
||||
const yesterdayMs = today.getTime() - 86400000
|
||||
const pendingPast = list.filter((it) => {
|
||||
if (it.status !== '待定') return false
|
||||
const t = new Date(it.date + 'T00:00:00').getTime()
|
||||
return Number.isFinite(t) && t <= yesterdayMs
|
||||
})
|
||||
console.log('[cron] 待定且已过的调价日: ' + pendingPast.length + ' 个')
|
||||
if (pendingPast.length === 0) {
|
||||
console.log('[cron] 油价历史: 0 命中, 跳过 apizero 调用')
|
||||
return
|
||||
}
|
||||
const r = await history.fetchAndStore()
|
||||
console.log('[cron] 油价历史入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped + ' (date=' + r.date + ')')
|
||||
} catch (e) {
|
||||
if (e instanceof ApizeroError) {
|
||||
console.error('[cron] 油价历史 apizero 错误: code=' + e.code + ' msg=' + e.message)
|
||||
} else {
|
||||
console.error('[cron] 油价历史任务失败:', e.message)
|
||||
}
|
||||
await alertError('浙江油价历史抓取', e.message)
|
||||
}
|
||||
}, { timezone: 'Asia/Shanghai' })
|
||||
console.log('[cron] 已注册: 每天 06:10 浙江油价历史抓取')
|
||||
}
|
||||
|
||||
// 每小时兜底: 任务自动归档扫描 (T-ARC-02; 项目加载时另有读时惰性执行)
|
||||
function scheduleAutoArchive() {
|
||||
cron.schedule('30 * * * *', async () => {
|
||||
try {
|
||||
const { runAutoArchiveSweep } = await import('./task-domain/archive.js')
|
||||
const n = await runAutoArchiveSweep()
|
||||
if (n > 0) console.log('[cron] 自动归档:', n, '条')
|
||||
} catch (e) {
|
||||
console.error('[cron] 自动归档失败:', e.message)
|
||||
await alertError('任务自动归档', e.message)
|
||||
}
|
||||
}, { timezone: 'Asia/Shanghai' })
|
||||
console.log('[cron] 已注册: 每小时 30 分 任务自动归档扫描')
|
||||
}
|
||||
|
||||
scheduleNews()
|
||||
scheduleOilHistory()
|
||||
scheduleAutoArchive()
|
||||
import cron from 'node-cron'
|
||||
|
||||
// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载,
|
||||
// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。
|
||||
async function importNewsFetcher() {
|
||||
const mod = await import('../scripts/fetchers/news.fetcher.js')
|
||||
return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews }
|
||||
}
|
||||
|
||||
async function alertError(taskName, message) {
|
||||
try {
|
||||
const { insertCronAlert } = await import('../scripts/lib/cron-alert.js')
|
||||
await insertCronAlert(taskName, message)
|
||||
} catch (e) {
|
||||
console.error('[cron] 报警写入失败(忽略):', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 每天 6:00 (Asia/Shanghai) 拉取新闻, 出错生成报警
|
||||
function scheduleNews() {
|
||||
cron.schedule('0 6 * * *', async () => {
|
||||
console.log('[cron] 6:00 触发')
|
||||
try {
|
||||
const { fetchNewsList, persistNews } = await importNewsFetcher()
|
||||
const list = await fetchNewsList()
|
||||
const r = await persistNews(list)
|
||||
console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped)
|
||||
} catch (e) {
|
||||
console.error('[cron] 任务失败:', e.message)
|
||||
await alertError('新闻拉取', e.message)
|
||||
}
|
||||
}, { timezone: 'Asia/Shanghai' })
|
||||
console.log('[cron] 已注册: 每天 06:00 拉取新闻')
|
||||
}
|
||||
|
||||
// 每小时兜底: 任务自动归档扫描 (T-ARC-02; 项目加载时另有读时惰性执行)
|
||||
function scheduleAutoArchive() {
|
||||
cron.schedule('30 * * * *', async () => {
|
||||
try {
|
||||
const { runAutoArchiveSweep } = await import('./task-domain/archive.js')
|
||||
const n = await runAutoArchiveSweep()
|
||||
if (n > 0) console.log('[cron] 自动归档:', n, '条')
|
||||
} catch (e) {
|
||||
console.error('[cron] 自动归档失败:', e.message)
|
||||
await alertError('任务自动归档', e.message)
|
||||
}
|
||||
}, { timezone: 'Asia/Shanghai' })
|
||||
console.log('[cron] 已注册: 每小时 30 分 任务自动归档扫描')
|
||||
}
|
||||
|
||||
scheduleNews()
|
||||
scheduleAutoArchive()
|
||||
|
||||
Reference in New Issue
Block a user