From becf0364ad6df52fd19f793452a924ccbda0de26 Mon Sep 17 00:00:00 2001 From: cheney Date: Sun, 26 Apr 2026 11:41:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=AF=94=E5=9B=BE=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/api/sh300-index.js | 27 ++++----- pages/strategy.js | 24 +++++--- scripts/fetch-one-year.js | 79 +++++++++++++++++++++++++ scripts/scheduler/index.js | 32 +++++++++- scripts/scraper/sh300.scraper.js | 44 ++++++++++++-- scripts/scraper/usd-cny-rate.scraper.js | 44 ++++++++++++-- 6 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 scripts/fetch-one-year.js diff --git a/pages/api/sh300-index.js b/pages/api/sh300-index.js index 60bf102..c7e79d5 100644 --- a/pages/api/sh300-index.js +++ b/pages/api/sh300-index.js @@ -22,30 +22,27 @@ async function handler(req, res) { const params = []; const conditions = []; - if (startDate) { - conditions.push('date >= ?'); - params.push(startDate); - } - - if (endDate) { - conditions.push('date <= ?'); - params.push(endDate); + if (startDate && endDate) { + conditions.push('date BETWEEN ? AND ?'); + params.push(startDate, endDate); } if (conditions.length > 0) { query += ' WHERE ' + conditions.join(' AND '); } - query += ' ORDER BY date ASC'; - - if (limit) { - query += ' LIMIT ?'; - params.push(parseInt(limit)); - } + query += ' ORDER BY date DESC'; const result = await pool.query(query, params); - const data = result.map(row => { + // 限制返回数量,默认为30 + const limitValue = limit ? parseInt(limit) : 30; + const limitedResult = result.slice(0, limitValue); + + // 反转顺序,使日期从早到晚 + const sortedResult = limitedResult.reverse(); + + const data = sortedResult.map(row => { let dateStr; if (row.date instanceof Date) { dateStr = row.date.toISOString().split('T')[0]; diff --git a/pages/strategy.js b/pages/strategy.js index 5143afc..b6a1a8a 100644 --- a/pages/strategy.js +++ b/pages/strategy.js @@ -50,12 +50,6 @@ export default function Strategy() { } ] - useEffect(() => { - if (selectedStrategy === 'sh300-exchange-rate') { - fetchSh300ExchangeData() - } - }, [selectedStrategy, timeRange, startDate, endDate]) - const handleTimeRangeChange = (range, start, end) => { setTimeRange(range) setStartDate(start) @@ -63,6 +57,7 @@ export default function Strategy() { } const fetchSh300ExchangeData = async () => { + console.log('fetchSh300ExchangeData called') setSh300Loading(true) try { let sh300Url = '/api/sh300-index' @@ -85,13 +80,19 @@ export default function Strategy() { exchangeUrl += '?' + params.join('&') } + console.log('Fetching from:', sh300Url, exchangeUrl) + const [sh300Res, exchangeRes] = await Promise.all([ fetch(sh300Url), fetch(exchangeUrl) ]) + console.log('Response status:', sh300Res.status, exchangeRes.status) + const sh300Json = await sh300Res.json() const exchangeJson = await exchangeRes.json() + console.log('Response data:', sh300Json, exchangeJson) + if (sh300Json.success && exchangeJson.success) { const sh300Map = new Map(sh300Json.data.map(item => [item.date, item.close])) const exchangeMap = new Map(exchangeJson.data.map(item => [item.date, item.rate])) @@ -104,6 +105,7 @@ export default function Strategy() { exchangeRate: exchangeMap.get(date) || null })).filter(item => item.sh300 !== null && item.exchangeRate !== null) + console.log('Merged data:', mergedData) setSh300ExchangeData(mergedData) } } catch (error) { @@ -113,6 +115,12 @@ export default function Strategy() { } } + useEffect(() => { + if (selectedStrategy === 'sh300-exchange-rate') { + fetchSh300ExchangeData() + } + }, [selectedStrategy, timeRange, startDate, endDate]) + return (
@@ -371,8 +379,8 @@ export default function Strategy() { - - value.toFixed(4)} /> + + value.toFixed(4)} /> diff --git a/scripts/fetch-one-year.js b/scripts/fetch-one-year.js new file mode 100644 index 0000000..a41d91e --- /dev/null +++ b/scripts/fetch-one-year.js @@ -0,0 +1,79 @@ + +import { spawn } from 'child_process'; + +async function runScraper(scraperPath, startDate, endDate) { + return new Promise((resolve, reject) => { + const args = [scraperPath]; + if (startDate) { + args.push('--start', startDate); + } + if (endDate) { + args.push('--end', endDate); + } + + const proc = spawn('bun', args, { stdio: 'inherit' }); + proc.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Process exited with code ${code}`)); + } + }); + proc.on('error', (err) => { + reject(err); + }); + }); +} + +async function fetchOneYearData() { + console.log('='.repeat(70)); + console.log('📊 获取近一年数据'); + console.log('='.repeat(70)); + + const currentDate = new Date(); + const startDate = new Date(currentDate); + startDate.setFullYear(startDate.getFullYear() - 1); + + const startDateStr = startDate.toISOString().split('T')[0]; + const endDateStr = currentDate.toISOString().split('T')[0]; + + console.log(`时间范围: ${startDateStr} 至 ${endDateStr}`); + console.log(''); + + console.log('='.repeat(70)); + console.log('💱 开始获取 USD-CNY 汇率数据...'); + console.log('='.repeat(70)); + + let usdSuccess = false; + try { + await runScraper('./scraper/usd-cny-rate.scraper.js', startDateStr, endDateStr); + console.log(`✅ USD-CNY 汇率数据更新完成`); + usdSuccess = true; + } catch (error) { + console.error(`❌ USD-CNY 汇率数据更新失败:`, error.message); + } + + console.log(''); + console.log('='.repeat(70)); + console.log('📈 开始获取沪深300指数数据...'); + console.log('='.repeat(70)); + + let sh300Success = false; + try { + await runScraper('./scraper/sh300.scraper.js', startDateStr, endDateStr); + console.log(`✅ 沪深300指数数据更新完成`); + sh300Success = true; + } catch (error) { + console.error(`❌ 沪深300指数数据更新失败:`, error.message); + } + + console.log(''); + console.log('='.repeat(70)); + console.log('📊 数据获取完成'); + console.log('='.repeat(70)); + console.log(`USD-CNY 汇率: ${usdSuccess ? '✅ 成功' : '❌ 失败'}`); + console.log(`沪深300指数: ${sh300Success ? '✅ 成功' : '❌ 失败'}`); + console.log('='.repeat(70)); +} + +fetchOneYearData(); diff --git a/scripts/scheduler/index.js b/scripts/scheduler/index.js index fe969d4..52740fd 100644 --- a/scripts/scheduler/index.js +++ b/scripts/scheduler/index.js @@ -1,12 +1,13 @@ import cron from 'node-cron'; import { trigger as triggerUSDCNYRate } from '../scraper/usd-cny-rate.scraper.js'; +import { trigger as triggerSH300Data } from '../scraper/sh300.scraper.js'; console.log('='.repeat(70)); console.log('🚀 启动定时任务调度器'); console.log('='.repeat(70)); -// 注册定时任务 -const job = cron.schedule('0 0 * * *', async () => { +// 注册定时任务 - USD-CNY 汇率数据 +const jobUSDCNY = cron.schedule('0 0 * * *', async () => { console.log('\n' + '='.repeat(70)); console.log('⏰ 执行定时任务: USD-CNY 汇率数据更新'); console.log('📅 执行时间:', new Date().toISOString()); @@ -25,10 +26,34 @@ const job = cron.schedule('0 0 * * *', async () => { timezone: 'Asia/Shanghai' }); +// 注册定时任务 - 沪深300指数数据 +const jobSH300 = cron.schedule('0 0 * * *', async () => { + console.log('\n' + '='.repeat(70)); + console.log('⏰ 执行定时任务: 沪深300指数数据更新'); + console.log('📅 执行时间:', new Date().toISOString()); + console.log('='.repeat(70)); + + try { + await triggerSH300Data(); + console.log('\n✅ 任务执行成功!'); + } catch (error) { + console.error('\n❌ 任务执行失败:', error.message); + } finally { + console.log('='.repeat(70)); + } +}, { + scheduled: true, + timezone: 'Asia/Shanghai' +}); + console.log('✅ 定时任务已启动: USD-CNY 汇率数据更新'); console.log('📅 执行表达式: 0 0 * * * (每天 0 点)'); console.log('📅 下次执行时间: 明天 00:00'); console.log(''); +console.log('✅ 定时任务已启动: 沪深300指数数据更新'); +console.log('📅 执行表达式: 0 0 * * * (每天 0 点)'); +console.log('📅 下次执行时间: 明天 00:00'); +console.log(''); console.log('按 Ctrl+C 停止...'); console.log('='.repeat(70)); @@ -38,7 +63,8 @@ process.on('SIGINT', () => { console.log('🛑 停止定时任务调度器'); console.log('='.repeat(70)); - job.stop(); + jobUSDCNY.stop(); + jobSH300.stop(); console.log('✅ 定时任务已停止'); console.log('\n' + '='.repeat(70)); diff --git a/scripts/scraper/sh300.scraper.js b/scripts/scraper/sh300.scraper.js index 959b54b..ccd3497 100644 --- a/scripts/scraper/sh300.scraper.js +++ b/scripts/scraper/sh300.scraper.js @@ -265,13 +265,50 @@ export async function trigger(startDate = null, endDate = null) { return await checkAndFillSH300Data(startDate, endDate); } -// 直接运行测试 +function parseCommandLineArgs() { + const args = process.argv.slice(2); + let startDate = null; + let endDate = null; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--start' && i + 1 < args.length) { + startDate = args[i + 1]; + i++; + } else if (args[i] === '--end' && i + 1 < args.length) { + endDate = args[i + 1]; + i++; + } else if (args[i] === '--help') { + console.log('使用方法:'); + console.log(' bun sh300.scraper.js [选项]'); + console.log(''); + console.log('选项:'); + console.log(' --start YYYY-MM-DD 指定开始日期'); + console.log(' --end YYYY-MM-DD 指定结束日期'); + console.log(' --help 显示帮助信息'); + console.log(''); + console.log('示例:'); + console.log(' bun sh300.scraper.js'); + console.log(' bun sh300.scraper.js --start 2024-01-01 --end 2024-01-31'); + process.exit(0); + } + } + + return { startDate, endDate }; +} + async function runTest() { console.log('='.repeat(60)); console.log('测试 sh300.scraper.js'); console.log('='.repeat(60)); - // 测试配置读取 + const { startDate, endDate } = parseCommandLineArgs(); + + if (startDate) { + console.log(`\n使用命令行参数:`); + console.log(` 开始日期: ${startDate}`); + console.log(` 结束日期: ${endDate || '默认'}`); + } + console.log('\n测试数据库配置读取...'); const config = loadDatabaseConfig(); console.log('配置读取结果:'); @@ -282,7 +319,7 @@ async function runTest() { console.log('配置读取测试完成!'); try { - const result = await trigger(); + const result = await trigger(startDate, endDate); console.log('\n' + '='.repeat(60)); console.log('测试结果:'); console.log(` 开始日期: ${result.startDate}`); @@ -298,5 +335,4 @@ async function runTest() { } } -// 运行测试 runTest(); diff --git a/scripts/scraper/usd-cny-rate.scraper.js b/scripts/scraper/usd-cny-rate.scraper.js index 43ed9a0..e56e491 100644 --- a/scripts/scraper/usd-cny-rate.scraper.js +++ b/scripts/scraper/usd-cny-rate.scraper.js @@ -262,13 +262,50 @@ export async function trigger(startDate = null, endDate = null) { return await checkAndFillUSDCNYRate(startDate, endDate); } -// 直接运行测试 +function parseCommandLineArgs() { + const args = process.argv.slice(2); + let startDate = null; + let endDate = null; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--start' && i + 1 < args.length) { + startDate = args[i + 1]; + i++; + } else if (args[i] === '--end' && i + 1 < args.length) { + endDate = args[i + 1]; + i++; + } else if (args[i] === '--help') { + console.log('使用方法:'); + console.log(' bun usd-cny-rate.scraper.js [选项]'); + console.log(''); + console.log('选项:'); + console.log(' --start YYYY-MM-DD 指定开始日期'); + console.log(' --end YYYY-MM-DD 指定结束日期'); + console.log(' --help 显示帮助信息'); + console.log(''); + console.log('示例:'); + console.log(' bun usd-cny-rate.scraper.js'); + console.log(' bun usd-cny-rate.scraper.js --start 2024-01-01 --end 2024-01-31'); + process.exit(0); + } + } + + return { startDate, endDate }; +} + async function runTest() { console.log('='.repeat(60)); console.log('测试 usd-cny-rate.scraper.js'); console.log('='.repeat(60)); - // 测试配置读取 + const { startDate, endDate } = parseCommandLineArgs(); + + if (startDate) { + console.log(`\n使用命令行参数:`); + console.log(` 开始日期: ${startDate}`); + console.log(` 结束日期: ${endDate || '默认'}`); + } + console.log('\n测试数据库配置读取...'); const config = loadDatabaseConfig(); console.log('配置读取结果:'); @@ -279,7 +316,7 @@ async function runTest() { console.log('配置读取测试完成!'); try { - const result = await trigger(); + const result = await trigger(startDate, endDate); console.log('\n' + '='.repeat(60)); console.log('测试结果:'); console.log(` 开始日期: ${result.startDate}`); @@ -295,5 +332,4 @@ async function runTest() { } } -// 运行测试 runTest();