添加汇率数据
This commit is contained in:
parent
b61cf9628e
commit
d232591a38
2
.env
2
.env
@ -1 +1 @@
|
||||
DATABASE_URL="mysql://root:fullstack@192.168.111.111:3306/iboard"
|
||||
DATABASE_URL="mysql://root:fullstack@192.168.111.111:3306/iboard"
|
||||
@ -32,6 +32,10 @@ COPY --from=builder /app/public* ./public/
|
||||
# 设置淘宝源并安装生产依赖
|
||||
RUN npm config set registry https://registry.npmmirror.com && npm install --only=production
|
||||
|
||||
|
||||
ENV DATABASE_URL="mysql://root:fullstack@baishe.vps.honor3.com:6033/iboard"
|
||||
|
||||
|
||||
# 暴露 3000 端口
|
||||
EXPOSE 3000
|
||||
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"mariadb": "^3.5.2",
|
||||
"marked": "^17.0.6",
|
||||
"next": "^14.2.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"prisma": "^7.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -2117,6 +2118,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-4.2.1.tgz",
|
||||
"integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "iboard",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
@ -14,6 +15,7 @@
|
||||
"mariadb": "^3.5.2",
|
||||
"marked": "^17.0.6",
|
||||
"next": "^14.2.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"prisma": "^7.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
105
pages/api/exchange-rate.js
Normal file
105
pages/api/exchange-rate.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { createPool } from 'mariadb';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function loadDatabaseConfig() {
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
if (fs.existsSync(envPath)) {
|
||||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||||
const lines = envContent.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('DATABASE_URL=')) {
|
||||
const url = trimmedLine.substring('DATABASE_URL='.length).replace(/^"|"$/g, '');
|
||||
|
||||
// 解析URL
|
||||
const parts = url.split('://')[1].split('@');
|
||||
const auth = parts[0].split(':');
|
||||
const hostAndDb = parts[1].split('/');
|
||||
const hostAndPort = hostAndDb[0].split(':');
|
||||
|
||||
return {
|
||||
user: auth[0],
|
||||
password: auth[1],
|
||||
host: hostAndPort[0],
|
||||
port: parseInt(hostAndPort[1]),
|
||||
database: hostAndDb[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
host: '192.168.111.111',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'fullstack',
|
||||
database: 'iboard'
|
||||
};
|
||||
}
|
||||
|
||||
let pool;
|
||||
|
||||
async function initPool() {
|
||||
if (!pool) {
|
||||
const config = loadDatabaseConfig();
|
||||
pool = createPool({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
connectionLimit: 5
|
||||
});
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
try {
|
||||
await initPool();
|
||||
|
||||
// 获取查询参数
|
||||
const { startDate, endDate, limit = 30 } = req.query;
|
||||
|
||||
let query = 'SELECT date, centerPrice FROM econ_ExchangeRate WHERE currency = ? ORDER BY date DESC';
|
||||
const params = ['USD/CNY'];
|
||||
|
||||
if (startDate && endDate) {
|
||||
query = 'SELECT date, centerPrice FROM econ_ExchangeRate WHERE currency = ? AND date BETWEEN ? AND ? ORDER BY date DESC';
|
||||
params.push(startDate, endDate);
|
||||
}
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
|
||||
// 限制返回数量
|
||||
const limitedResult = result.slice(0, parseInt(limit));
|
||||
|
||||
// 反转顺序,使日期从早到晚
|
||||
const sortedResult = limitedResult.reverse();
|
||||
|
||||
// 格式化数据
|
||||
const formattedData = sortedResult.map(row => ({
|
||||
date: row.date instanceof Date ? row.date.toISOString().split('T')[0] : row.date,
|
||||
rate: parseFloat(row.centerPrice)
|
||||
}));
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: formattedData,
|
||||
total: result.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取汇率数据失败:', error.message);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取汇率数据失败',
|
||||
error: error.message
|
||||
});
|
||||
} finally {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Head from 'next/head'
|
||||
import Navbar from '../components/Navbar'
|
||||
import Footer from '../components/Footer'
|
||||
@ -159,6 +159,34 @@ const sowInventoryData = [
|
||||
|
||||
export default function Data() {
|
||||
const [selectedIndicator, setSelectedIndicator] = useState('bond-china-treasury')
|
||||
const [exchangeRateData, setExchangeRateData] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedIndicator === 'central-bank-usd-cny-rate') {
|
||||
fetchExchangeRateData()
|
||||
}
|
||||
}, [selectedIndicator])
|
||||
|
||||
const fetchExchangeRateData = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await fetch('/api/exchange-rate?limit=60')
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setExchangeRateData(data.data)
|
||||
} else {
|
||||
setError(data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
setError('获取汇率数据失败')
|
||||
console.error('获取汇率数据失败:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const indicators = [
|
||||
{
|
||||
@ -180,7 +208,8 @@ export default function Data() {
|
||||
{ id: 'central-bank-china-gold-monthly', name: '中国黄金储备(月)' },
|
||||
{ id: 'central-bank-us-gold-monthly', name: '美国黄金储备(月)' },
|
||||
{ id: 'central-bank-china-gold-yearly', name: '中国黄金储备(年)' },
|
||||
{ id: 'central-bank-us-gold-yearly', name: '美国黄金储备(年)' }
|
||||
{ id: 'central-bank-us-gold-yearly', name: '美国黄金储备(年)' },
|
||||
{ id: 'central-bank-usd-cny-rate', name: '人民币兑美元汇率' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -526,6 +555,54 @@ export default function Data() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedIndicator === 'central-bank-usd-cny-rate' && (
|
||||
<div className="indicator-detail">
|
||||
<h2 className="indicator-title">人民币兑美元汇率</h2>
|
||||
<p className="indicator-description">展示人民币兑美元汇率的变化趋势</p>
|
||||
|
||||
{loading ? (
|
||||
<div className="loading">加载中...</div>
|
||||
) : error ? (
|
||||
<div className="error">{error}</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="chart-container">
|
||||
<ResponsiveContainer width="100%" height={500}>
|
||||
<LineChart data={exchangeRateData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis domain={['dataMin - 0.1', 'dataMax + 0.1']} tickFormatter={(value) => value.toFixed(4)} />
|
||||
<Tooltip formatter={(value) => [value.toFixed(4), '汇率']} />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="rate" name="汇率" stroke="#1e88e5" strokeWidth={2} activeDot={{ r: 8 }} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="data-table-container">
|
||||
<h3 className="table-title">详细数据</h3>
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日期</th>
|
||||
<th>汇率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{exchangeRateData.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td>{row.date}</td>
|
||||
<td>{row.rate.toFixed(4)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedIndicator === 'commodity-pork-price' && (
|
||||
<div className="indicator-detail">
|
||||
<h2 className="indicator-title">猪肉价格</h2>
|
||||
|
||||
49
scripts/scheduler/index.js
Normal file
49
scripts/scheduler/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
import cron from 'node-cron';
|
||||
import { trigger as triggerUSDCNYRate } from '../scraper/usd-cny-rate.scraper.js';
|
||||
|
||||
console.log('='.repeat(70));
|
||||
console.log('🚀 启动定时任务调度器');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
// 注册定时任务
|
||||
const job = cron.schedule('0 0 * * *', async () => {
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('⏰ 执行定时任务: USD-CNY 汇率数据更新');
|
||||
console.log('📅 执行时间:', new Date().toISOString());
|
||||
console.log('='.repeat(70));
|
||||
|
||||
try {
|
||||
await triggerUSDCNYRate();
|
||||
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('按 Ctrl+C 停止...');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
// 优雅处理退出
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('🛑 停止定时任务调度器');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
job.stop();
|
||||
console.log('✅ 定时任务已停止');
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('📊 定时任务调度器已停止');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
49
scripts/scheduler/test.js
Normal file
49
scripts/scheduler/test.js
Normal file
@ -0,0 +1,49 @@
|
||||
import cron from 'node-cron';
|
||||
import { trigger as triggerUSDCNYRate } from '../scraper/usd-cny-rate.scraper.js';
|
||||
|
||||
console.log('='.repeat(70));
|
||||
console.log('🚀 启动定时任务调度器');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
// 注册定时任务
|
||||
const job = cron.schedule('0 0 * * *', async () => {
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('⏰ 执行定时任务: USD-CNY 汇率数据更新');
|
||||
console.log('📅 执行时间:', new Date().toISOString());
|
||||
console.log('='.repeat(70));
|
||||
|
||||
try {
|
||||
await triggerUSDCNYRate();
|
||||
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('按 Ctrl+C 停止...');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
// 优雅处理退出
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('🛑 停止定时任务调度器');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
job.stop();
|
||||
console.log('✅ 定时任务已停止');
|
||||
|
||||
console.log('\n' + '='.repeat(70));
|
||||
console.log('📊 定时任务调度器已停止');
|
||||
console.log('='.repeat(70));
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
@ -1,23 +1,82 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { PrismaClient } from '../../generated/prisma/client.js';
|
||||
import { PrismaMariaDb } from '@prisma/adapter-mariadb';
|
||||
import { createPool } from 'mariadb';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function loadDatabaseConfig() {
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
if (fs.existsSync(envPath)) {
|
||||
console.log('读取 .env 文件...');
|
||||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||||
const lines = envContent.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('DATABASE_URL=')) {
|
||||
const url = trimmedLine.substring('DATABASE_URL='.length).replace(/^"|"$/g, '');
|
||||
console.log('从 .env 文件获取数据库配置成功!');
|
||||
console.log(`DATABASE_URL: ${url}`);
|
||||
|
||||
// 解析URL
|
||||
const parts = url.split('://')[1].split('@');
|
||||
const auth = parts[0].split(':');
|
||||
const hostAndDb = parts[1].split('/');
|
||||
const hostAndPort = hostAndDb[0].split(':');
|
||||
|
||||
return {
|
||||
user: auth[0],
|
||||
password: auth[1],
|
||||
host: hostAndPort[0],
|
||||
port: parseInt(hostAndPort[1]),
|
||||
database: hostAndDb[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('未找到 .env 文件或 DATABASE_URL 配置,使用默认配置');
|
||||
return {
|
||||
host: '192.168.111.111',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'fullstack',
|
||||
database: 'iboard'
|
||||
};
|
||||
}
|
||||
|
||||
// 创建数据库连接池
|
||||
const pool = createPool({
|
||||
host: '192.168.111.111',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'fullstack',
|
||||
database: 'iboard',
|
||||
connectionLimit: 5
|
||||
});
|
||||
const config = loadDatabaseConfig();
|
||||
console.log('数据库配置:');
|
||||
console.log(` 主机: ${config.host}`);
|
||||
console.log(` 端口: ${config.port}`);
|
||||
console.log(` 用户: ${config.user}`);
|
||||
console.log(` 数据库: ${config.database}`);
|
||||
|
||||
// 构造 PrismaClient,提供 adapter
|
||||
const prisma = new PrismaClient({
|
||||
adapter: new PrismaMariaDb(pool)
|
||||
});
|
||||
let pool;
|
||||
|
||||
async function initPool() {
|
||||
if (!pool) {
|
||||
pool = createPool({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
connectionLimit: 5
|
||||
});
|
||||
|
||||
try {
|
||||
const conn = await pool.getConnection();
|
||||
console.log('数据库连接成功!');
|
||||
conn.release();
|
||||
} catch (error) {
|
||||
console.error('数据库连接失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
// 浙江数据开放网站URL(示例,实际需要根据真实网站修改)
|
||||
const ZHEJIANG_OPEN_DATA_URL = 'https://data.zj.gov.cn/';
|
||||
@ -31,10 +90,9 @@ const MIN_UPDATE_INTERVAL = 30 * 60 * 1000;
|
||||
// 从数据库读取缓存
|
||||
async function readCache() {
|
||||
try {
|
||||
const cache = await prisma.scraperCache.findUnique({
|
||||
where: { scraperName: SCRAPER_NAME }
|
||||
});
|
||||
return cache || { scraperName: SCRAPER_NAME, lastDataHash: null, lastUpdateAt: null };
|
||||
await initPool();
|
||||
const result = await pool.query('SELECT * FROM ScraperCache WHERE scraperName = ?', [SCRAPER_NAME]);
|
||||
return result.length > 0 ? result[0] : { scraperName: SCRAPER_NAME, lastDataHash: null, lastUpdateAt: null };
|
||||
} catch (error) {
|
||||
console.error('读取数据库缓存失败:', error.message);
|
||||
return { scraperName: SCRAPER_NAME, lastDataHash: null, lastUpdateAt: null };
|
||||
@ -44,18 +102,11 @@ async function readCache() {
|
||||
// 写入数据库缓存
|
||||
async function writeCache(lastDataHash) {
|
||||
try {
|
||||
await prisma.scraperCache.upsert({
|
||||
where: { scraperName: SCRAPER_NAME },
|
||||
update: {
|
||||
lastDataHash,
|
||||
lastUpdateAt: new Date()
|
||||
},
|
||||
create: {
|
||||
scraperName: SCRAPER_NAME,
|
||||
lastDataHash,
|
||||
lastUpdateAt: new Date()
|
||||
}
|
||||
});
|
||||
await initPool();
|
||||
await pool.query(
|
||||
'INSERT INTO ScraperCache (scraperName, lastDataHash, lastUpdateAt) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE lastDataHash = VALUES(lastDataHash), lastUpdateAt = VALUES(lastUpdateAt)',
|
||||
[SCRAPER_NAME, lastDataHash, new Date()]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('写入数据库缓存失败:', error.message);
|
||||
}
|
||||
@ -76,12 +127,9 @@ function computeDataHash(data) {
|
||||
// 获取数据库中最新的月份
|
||||
async function getLatestMonth() {
|
||||
try {
|
||||
const latest = await prisma.econ_SowInventory.findFirst({
|
||||
orderBy: {
|
||||
month: 'desc'
|
||||
}
|
||||
});
|
||||
return latest ? latest.month : null;
|
||||
await initPool();
|
||||
const result = await pool.query('SELECT MAX(month) as latestMonth FROM Econ_SowInventory');
|
||||
return result[0].latestMonth || null;
|
||||
} catch (error) {
|
||||
console.error('查询最新月份失败:', error.message);
|
||||
return null;
|
||||
@ -108,6 +156,7 @@ function shouldSkipRequest(cache) {
|
||||
// 能繁母猪数量数据爬取函数(增量更新版本)
|
||||
async function scrapeSowInventory(incremental = true) {
|
||||
try {
|
||||
await initPool();
|
||||
console.log('='.repeat(50));
|
||||
console.log('开始爬取浙江省能繁母猪数量数据...');
|
||||
console.log(`增量更新模式: ${incremental ? '开启' : '关闭'}`);
|
||||
@ -224,26 +273,25 @@ async function scrapeSowInventory(incremental = true) {
|
||||
for (const item of dataToUpdate) {
|
||||
try {
|
||||
// 检查数据是否已存在
|
||||
const existing = await prisma.econ_SowInventory.findUnique({
|
||||
where: { month: item.month }
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
const existing = await pool.query('SELECT * FROM Econ_SowInventory WHERE month = ?', [item.month]);
|
||||
|
||||
if (existing.length > 0) {
|
||||
// 检查数据是否真的需要更新(值变化了才更新)
|
||||
if (existing.inventory !== item.inventory) {
|
||||
await prisma.econ_SowInventory.update({
|
||||
where: { month: item.month },
|
||||
data: { inventory: item.inventory }
|
||||
});
|
||||
if (existing[0].inventory !== item.inventory) {
|
||||
await pool.query(
|
||||
'UPDATE Econ_SowInventory SET inventory = ? WHERE month = ?',
|
||||
[item.inventory, item.month]
|
||||
);
|
||||
updateCount++;
|
||||
console.log(`更新数据: ${item.month} - ${item.inventory} (原值: ${existing.inventory})`);
|
||||
console.log(`更新数据: ${item.month} - ${item.inventory} (原值: ${existing[0].inventory})`);
|
||||
} else {
|
||||
console.log(`数据未变化,跳过: ${item.month}`);
|
||||
}
|
||||
} else {
|
||||
await prisma.econ_SowInventory.create({
|
||||
data: item
|
||||
});
|
||||
await pool.query(
|
||||
'INSERT INTO Econ_SowInventory (month, inventory) VALUES (?, ?)',
|
||||
[item.month, item.inventory]
|
||||
);
|
||||
newCount++;
|
||||
console.log(`新增数据: ${item.month} - ${item.inventory}`);
|
||||
}
|
||||
@ -272,10 +320,11 @@ async function scrapeSowInventory(incremental = true) {
|
||||
console.error('爬取数据时出错:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
// 关闭Prisma连接
|
||||
await prisma.$disconnect();
|
||||
// 关闭数据库连接池
|
||||
await pool.end();
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
console.log('数据库连接池已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,68 @@
|
||||
import { createPool } from 'mariadb';
|
||||
import { fetch as fetchUSDToCNYRate } from '../fetchers/usd-cny-rate.fetcher.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
let pool;
|
||||
|
||||
function loadDatabaseConfig() {
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
if (fs.existsSync(envPath)) {
|
||||
console.log('读取 .env 文件...');
|
||||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||||
const lines = envContent.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('DATABASE_URL=')) {
|
||||
const url = trimmedLine.substring('DATABASE_URL='.length).replace(/^"|"$/g, '');
|
||||
console.log('从 .env 文件获取数据库配置成功!');
|
||||
console.log(`DATABASE_URL: ${url}`);
|
||||
|
||||
// 解析URL
|
||||
const parts = url.split('://')[1].split('@');
|
||||
const auth = parts[0].split(':');
|
||||
const hostAndDb = parts[1].split('/');
|
||||
const hostAndPort = hostAndDb[0].split(':');
|
||||
|
||||
return {
|
||||
user: auth[0],
|
||||
password: auth[1],
|
||||
host: hostAndPort[0],
|
||||
port: parseInt(hostAndPort[1]),
|
||||
database: hostAndDb[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('未找到 .env 文件或 DATABASE_URL 配置,使用默认配置');
|
||||
return {
|
||||
host: '192.168.111.111',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'fullstack',
|
||||
database: 'iboard'
|
||||
};
|
||||
}
|
||||
|
||||
async function initPool() {
|
||||
if (!pool) {
|
||||
console.log('初始化数据库连接池...');
|
||||
const config = loadDatabaseConfig();
|
||||
|
||||
console.log('数据库配置:');
|
||||
console.log(` 主机: ${config.host}`);
|
||||
console.log(` 端口: ${config.port}`);
|
||||
console.log(` 用户: ${config.user}`);
|
||||
console.log(` 数据库: ${config.database}`);
|
||||
|
||||
pool = createPool({
|
||||
host: '192.168.111.111',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'fullstack',
|
||||
database: 'iboard',
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
connectionLimit: 5
|
||||
});
|
||||
|
||||
@ -212,12 +263,23 @@ export async function trigger(startDate = null, endDate = null) {
|
||||
}
|
||||
|
||||
// 直接运行测试
|
||||
console.log('='.repeat(60));
|
||||
console.log('测试 usd-cny-rate.scraper.js');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
trigger()
|
||||
.then(result => {
|
||||
async function runTest() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('测试 usd-cny-rate.scraper.js');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 测试配置读取
|
||||
console.log('\n测试数据库配置读取...');
|
||||
const config = loadDatabaseConfig();
|
||||
console.log('配置读取结果:');
|
||||
console.log(` 主机: ${config.host}`);
|
||||
console.log(` 端口: ${config.port}`);
|
||||
console.log(` 用户: ${config.user}`);
|
||||
console.log(` 数据库: ${config.database}`);
|
||||
console.log('配置读取测试完成!');
|
||||
|
||||
try {
|
||||
const result = await trigger();
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('测试结果:');
|
||||
console.log(` 开始日期: ${result.startDate}`);
|
||||
@ -227,8 +289,11 @@ trigger()
|
||||
console.log(` 最终数据: ${result.finalCount} 条`);
|
||||
console.log(` 操作成功: ${result.success}`);
|
||||
console.log('='.repeat(60));
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTest();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user